12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 361|回复: 0

android内核漏洞cve-2014-3153分析笔记

[复制链接]

59

主题

59

帖子

128

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
128
发表于 2020-2-9 02:08:28 | 显示全部楼层 |阅读模式
                                             Android内核提权cve-2014-3153研究笔记   一、简介   我这里把我自己的理解总结下,看别人的总是云山雾绕,不得要领。还是要有自己的思路。当然也希望自己写的通俗一些,那么又有一大批人能看懂了就。文中图片修改了文尾链接处作者的图片,部分例子采用参考中所得。各位想做下实验的可以参考我上一篇的编译过程,也可以看我给出的链接。
  受影响的Linux内核系统可能被直接DOS,精心设计可以获取根权限。这个漏洞利用的核心就是,通过两个流程bug造成程序栈中变量没有清理,然后利用栈内存共用修改栈值,最终绕过地址读写限制实现提权。
  二、触发机制
  科普下锁的概念:锁就是由于多线程同时访问资源会造成资源更改混乱而增加的概念。简单说,有一个公共资源。一个人在用的时候,其他人就要等着。不能两个或多个人同时用。
   这个漏洞利用Futex(Fast UserspacemuTEX)(是一种锁机制)的不同唤醒方式,绕过了栈数据清理。从而控制了流程。
   如何绕过?
漏洞利用了futex_requeue,futex_lock_pi,futex_wait_requeue_pi三个函数存在的两个bug位于futex.c
git clone https://android.googlesource.com/kernel/goldfish.git -bgoldfish3.4
cd goldfish
git checkout e8c92d268b8b8feb550ca8d24a92c1c98ed65acekernel/futex.c
可以自行下载一下。
   2.1.relockBUG
  relock漏洞源于futex_lock_pi函数(由futex_lock_pi_atomic实现),futex_lock_pi(&uaddr)调用之后,调用地址uaddr被锁住,只有利用解锁futex_unlock_pi后,才能被其他线程利用。
futex_lock_pi_atomic又是由cmpxchg_futex_value_locked(&curval,uaddr, 0,newval)实现并尝试去锁住uaddr。它的实现的含义是如果uaddr中存储的值为0,那么就说明没有线程占用锁,成功的获取到了锁,并将当前线程的id写进去。(uaddr是用户空间的一个整形变量,被用于Futex系统架构中的futex互斥量。uaddr的值与其用户空间的地址都会被Futex用到。)
  但是问题来了,既然uaddr是用户变量,那我们就可以手动设置为0.这时候地址上的锁其实是释放了,但上锁后的堆栈里的内容没有被清理。而且没有唤醒阻塞在锁上的线程,修改pi_state等。
  这样就可以利用通过手动设置uaddr=0的方式使两个线程同时获得锁。这个叫relock。可以叫多重上锁。
   2.2 requeueBUG
  futex_wait_requeue_pi的功能是让调用线程阻塞在uaddr1上,然后等待futex_requeue的唤醒。唤醒过程将所有阻塞在uaddr1上的线程全部移动到uaddr2上去。
syscall(__NR_futex, &uaddr1,FUTEX_WAIT_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); //在uaddr1上等待syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0,&uaddr2, uaddr1);//尝试获取uaddr2上的锁,然后唤醒uaddr1上等待的线程。如果uaddr2锁获取失败,则将被唤醒线程添加到uaddr2的rt_waiter列表上,进入线程进入内核等待。啥时候进入内核等待,我们下面讲。

进入内核等待方式图

   这个时候如果我们再次调用
syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0,&uaddr2, uaddr1)
将失败而直接返回,并不会进入系统调用。
  而requeueBUG允许我们在以上两条语句执行之后,首先设置uaddr2=0,然后执行这样的语句:
syscall(__NR_futex, &uaddr2, FUTEX_CMP_REQUEUE_PI, 1, 0,&uaddr2, uaddr2);
这个语句中所有地址都变成了uaddr2,也就是说将等待在uaddr2上的线程重排到uaddr2上,这是不合逻辑的,但是Futex没有检查这样的调用,也就是说没有检查uaddr1 ==uaddr2的情况,从而造成了我们可以二次进入futex_requeue中进行唤醒操作。我们的线程进入内核等待后本来需要用内核唤醒的方式,现在被篡改成了普通的唤醒方式。致使一部分的栈没有被清空。就是栈上的rt_waiter依然被连在rt_mutex的waiterlist上。

   2.3漏洞触发
这里还要了解一下futex_requeue中唤醒futex_wait_requeue_pi线程的两种方式:
  1.futex_proxy_trylock_atomic调用尝试获取uaddr2上的锁,如果成功,则唤醒等待线程,函数返回,否则继续执行。注意,这一步没有进入内核互斥量中,如果成功,将不进入内核互斥量,而是直接返回到用户空间,从而减小内核互斥量的开销;
  2.rt_mutex_start_proxy_lock尝试获取uaddr2锁,如果成功,则唤醒等待线程,如果失败,则将线程阻塞到uaddr2的内核互斥量上,将rt_waiter加入rt_mutex的waiterlist。

   我们来总结下正常程序的执行状态。
futex_wait_requeue_pi(uaddr1,uaddr2)等待被唤醒,正常情况下我们唤醒的方式要么在内核唤醒,要么普通的唤醒。这个要看uaddr2的锁状态。                           漏洞触发图
  但是我们这里利用uaddr2加锁使线程进入内核等待状态,然后relockBUG uaddr2=0,最后 requeueBUGfutex_wait_requeue_pi(uaddr2,uaddr2),使阻塞在内核等待的线程用普通方式唤醒。构造了程序的异常执行流。
   如何使一个线程按我们的方式执行如下图:                          异常流程构造图
1.我们使用主线程1futex_lock_pi锁住uaddr2。
2、3.创建线程2,等待被唤醒futex_requeue(uaddr1,uaddr2),uaddr2被锁,所以进入内核等待,futex_wait_requeue_pi中的rt_waiter加入到rt_waiter的waiterlist上。
4.利用relockBUG,将uaddr2赋值为0,释放uaddr2上的锁.
5.利用requeue漏洞,调用futex_requeue(uaddr2,uaddr2),uaddr2没锁,触发的普通唤醒模式。
导致rt_waiter没有被清理。
而至于这个栈上的没有owner的rt_waiter被链接在rt_mutex上,如果线程2结束,内核清理环境的时候,会去尝试唤醒这个rt_waiter,结果就是造成内核崩溃。  
二、提权过程
  上一节我们讲到了rt_waiter没有了owner,但是有什么用呢?
  这里我们会用一种机制来更改这个没有owner的rt_waiter的数据
   2.1栈内存共用问题
   #include  

void A(int val)
{
   int local;
   local =val;
   printf("A locacladdr =0x%x\n",&local);
}

void B(int val2)
{
   int local;
   printf("B locacladdr =0x%x\n",&local);
   printf("B local =%d\n",local);
}

int main()
{
   A(6);
   B(2);
   return 0;
}
这里用GCC编译,不进行优化
gcc -m32 foo.c -o foo -g
./foo

A locacladdr = 0xffd119b8
B locacladdr = 0xffd119b8
B local = 6
            图栈
我们可以看到,这里A,B函数的局部变量的地址是一样的。有堆栈概念的人都知道,我们的每调用一个函数就会产生一个新的堆栈。但是上一个调用函数的栈中的数据如果没销毁,下一个函数构造的栈中就能利用。我们就可非法篡改数据。如上图的实例。
哈,有啥用,我们可以直接调用A函数修改B函数中的数据。
   2.2修改内核中的数据
  我们可以调用另一个结构相似的函数修改我们的rt_waiter结构数据。我们选取__sys_sendmmsg函数。
有其他选择么?有。有一个分析栈空间的脚本,checkstack.pl的脚本,断到futex_wait_requeue_pi上可以看到很多函数。这里选择可以完成rt_waiter所在栈深度修改的一个。
   这里我们要修改的是链表。
   rt_waiter结构
type = struct rt_mutex_waiter{
   struct plist_nodelist_entry;
   struct plist_nodepi_list_entry;
   struct task_struct*task;
   struct rt_mutex *lock
}
plist结构
struct plist_node{
int prio;
struct list_head prio_list;//有个next和prev的指针
struct list_head node_lsit;//有个next和prev的指针
}

endmmsg的函数声明及主要结构如下:
int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned intvlen,
unsigned int flags);

struct mmsghdr {
struct msghdr msg_hdr;
unsigned int msg_len;
};

struct msghdr {
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
size_t msg_controllen;
int msg_flags;
};

struct iovec {
void *iov_base;
__kernel_size_t iov_len;
};
其中位置重叠部分如图

看下我们锁机制中链表的形式是这样的

我们利用内核锁的唤醒在内核中插入链表,这个插入的位置可以根据prio参数来选择,因为程序会按顺序排,我们只要适当的修改prio参数即可。虽然可以更改内核中的值了,但这个地址内核地址不可控。怎么利用?
这里分两步:
   (一)、内核任意地址写入值(写入的值不可控)
   我们在用户态地址上利用mmap构建一个rt_waiter的结构fake_node。如图  
在内核中把rt_waiter指向用户态的fake_node.这时候我们我们在用户态的fake_node就可以随意指定内核地址.

假设我们要修改内核地址A的值,我们就把fake_node的node.prev指向(A-offset),这里offset=sizeof(prio)+sizeof(list_head);我们把A-offset当成了一个plist结构。其实没人知道是不是。这个时候再利用漏洞在A节点和fake_node之间插入一个内核节点,那么A节点的node.prev就指向了新节点的地址,虽然这个地址我们暂时不可控,但我们实现了任意内核地址A写入数据。
   (二).实现线程任意地址可读写
    我们这里需要找到特定线程的thread_info,方法很简单线程任意栈地址与上0xffffe000。这个位置是固定的。thread_info的地址,再定义正确的thread_info的结构,就可以得到addr_limit的地址了。addr_limit是限制我们访问空间地址位置的,限制在哪,我们就只能读小于它的地址,只要我们把它改成0xffffffff。我们就可以实现,任意地址的读写。
目测不容易。我们现在只能实现任意地址写,但地址上写了啥,还不知道。
   没关系,我们这里创建两个线程A,B.线程B循环创建。


A实现循环读取addr_limit的值,显然开始的时候读不到,就一直读着。线程B利用任意地址写值得方式把自己的不可控的rt_wait地址写到A的addr_limit中,由于内核中不同线程栈位置不同。我们的线程B不断的创建,总有机会得到一个比A线程高的地址。只要我们把这个高地址写到A线程的addr_limit中,那么线程A的addr_limit位置就能任意改写了。(不同线程使用不同位置的栈)
简单说就是先利用内核漏洞把addr_limit值的改到比本线程高的值,用户态可以改写了,然后直接在用户态addr_limit=0xffffffff.这下任意内核地址就都可以读写了。
   2.3、内核提权
  thread_info包含了线程的主要信息,当然也就包括了线程的task_struct。而task_struct结构体包含了该线程的所有信息。这其中就包括权限方面的重要证书信息cred,该结构体是线程权限的管理者,标识了当前线程的权限。我们只要如下更改:
credbuf.uid = 0;
credbuf.gid = 0;
credbuf.suid = 0;
credbuf.sgid = 0;
credbuf.euid = 0;
credbuf.egid = 0;
credbuf.fsuid = 0;
credbuf.fsgid = 0;
credbuf.cap_inheritable.cap[0] = 0xffffffff;
credbuf.cap_inheritable.cap[1] = 0xffffffff;
credbuf.cap_permitted.cap[0] = 0xffffffff;
credbuf.cap_permitted.cap[1] = 0xffffffff;
credbuf.cap_effective.cap[0] = 0xffffffff;
credbuf.cap_effective.cap[1] = 0xffffffff;
credbuf.cap_bset.cap[0] = 0xffffffff;
credbuf.cap_bset.cap[1] = 0xffffffff;
securitybuf.osid = 1;
securitybuf.sid = 1;
taskbuf.pid = 1;

三、总结
  本文利用自己的思路将CVE-2014-3153漏洞利用过程整理了下,整个过程所获很多,把自己很多连不起来的知识融汇了一下。下面总结下:
  1.分析得出两个BUG,利用bug实现了,实现了内核栈上残留有效数据。
  2.利用栈内存共用问题,实现了内核栈数据的更改。(修改的值不可控)
  3.通过多线程配合实现了内核数据任意读写从而提权。



参考:
1.http://blog.topsec.com.cn/ad_lab/cve2014-3153/
2.《漏洞战争》CVE-2014-3153Android内核Futex提取漏洞
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-25 00:01 , Processed in 0.078125 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表