Android Root 1 —— RageAgainstTheCage

这个漏洞网上有很多资料,我也来总结一下。
首先,先来大致了解下Root Android系统的原理。Root的本质就是修改替换/system/bin/su这个文件。这个文件就跟桌面linux的su命令一样,提供切换到root用户的功能。但是系统自带的su文件(有时候系统中甚至不会有这个文件)有诸多限制,不可能让随便一个程序就能使用su命令。该文件的源码在/system/extras/su/目录下,其中有这样一段代码:



[cpp] view plaincopyprint?
01./* Until we have something better, only root and the shell can use su. */ 
02.myuid = getuid(); 
03.if (myuid != AID_ROOT && myuid != AID_SHELL) { 
04.    fprintf(stderr,"su: uid %d not allowed to su\n", myuid); 
05.    return 1; 
06.} 

从这个 if 我们能够看到它对不是root和shell的用户进行了排除(android中每个普通应用程序所属的用户id应该都是临时用户?)。所以我们只要用我们修改后的su文件将系统中的替换即可。修改su时除了修改它的源代码,它的权限也要进行修改——需要设置上SUID和GUID位(这两位有什么作用我这里就不细说了,网上很多资料)。

但是,/system/bin/这个目录的权限是644,用户和用户组均是root,所以你也不可能直接删除修改su文件,而是需要获得root权限才能删除修改su,这就形成了死循环了。所以这里root的关键点就是怎样获得root权限来修改su,答案当然就是漏洞了。

也许有人会问了,既然你都已经通过漏洞获得root权限了,为什么还要通过修改su来获得root权限呢,这不多此一举吗?因为这里的漏洞有点特殊:这次我们讲RageAgainstTheCage这个漏洞,它只是临时的一次性的获取root权限,当你手机重启后就还原了,更重要的是,它需要手机通过USB连接电脑时才能获得root权限,这让我们平时需要root需要使用手机带来了不方便,总不可能每次需要root的时候都去连接电脑执行一次漏洞程序吧。所以我们的策略是使用那个“不方便的一次性的root”来修改su使得我们能够获得“永久root的权限”。

下面我们来看RageAgainstTheCage这个漏洞的原理。

既然是漏洞,那么是哪里的漏洞呢?难道是linux内核漏洞?那是不可能的,如果真是linux内核漏洞,那么世界早就乱套了。既然Android是基于linux的系统,那么肯定是android自己的问题了。

Android系统有一个服务程序:adb(Android Debug Bridge),我们这里利用到的漏洞就是adb程序的一个漏洞。adb是一个“客户端-服务器端 ”程序,其中客户端是你用来操作的电脑,服务器端是android设备;adb也是android sdk里的一个工具,用于连接、调试、管理android设备。一般来说当手机通过usb连接到电脑后(当然,还得需要该手机的驱动才行),系统会自动启动手机端的adb程序,这样我们在电脑上的命令行里输入命令就能连接控制手机了,例如我们在开发程序时在命令行里输入的”adb devices“、”adb install ***“、”adb shell“等。adb程序就是为这个功能提供服务支持。其中adb shell很有用,它为我们提供了一个登陆手机的shell,可惜它也没有权限使用su命令(虽然它是shell权限,通过了上面代码中的 if 条件判断,但是后面还有其他验证,看来普通shell也是不能使用su的啊,还是 root 靠谱)。

明白了adb是个什么东西后,我们再来看看adb的源代码,分析其中到底哪里有漏洞。adb的源代码在/system/core/adb/adb.c,简要如下:




[cpp] view plaincopyprint?
01.int adb_main(int is_daemon)  
02.{  
03.      ......  
04.      property_get("ro.secure", value, "");  
05.      if (strcmp(value, "1") == 0) {  
06.            secure = 1;  
07.      ......  
08.      }  
09.     if (secure) {  
10.           ......  
11.           setgid(AID_SHELL);  
12.           setuid(AID_SHELL);  
13.           ......  
14.      }  
15.} 

Adbd服务进程初始是Root权限,因为它要进行一些系统的初始化的工作,这时必须要是root权限才行。初始化完成后它会给自己降级为shell,所以我们在使用adb shell时看到的权限是shell,而不是root。降级的关键代码是setgid()和setuid()这两个函数,而漏洞就出在这里。

试想一下,若是降级失败会怎样?很明显,若是降级失败,函数setgid()和setuid()都返回非0值,但是,程序并没有检查它的返回值,也就是并没有判断它是否降级成功,这就给了我们可乘之机。若是降级失败,程序继续运行,并没有任何出错的地方,唯一的不同就是这个服务程序依然是root权限,而当我们使用adb shell得到它返回给我们使用的shell窗口时,我们得到的也就是root权限的shell。

为什么?因为它使用fork()函数来产生子进程(这个子进程就是我们得到的shell),而在linux中子进程将继承父进程的权限,所以我们得到的shell就是具有root权限,显然,此时我们就能很方便地删除修改su文件了。

那么,其中的关键点就是:怎么让它降级失败,也就是setgid()和setuid()函数执行失败。在此之前我们来补充一个知识点。

linux系统中,任何一个用户他所能拥有的进程数量是有限的,这个上限为RLIMIT_NPROC(我电脑上这个值大概是6万多),平时大家都很少接触这个东西,因为正常作业时我们每个用户所拥有的进程数量基本不会超过它。一旦达到这个上限,那么系统就不会再为此用户添加新的进程。

结合之前的分析,我们就可以利用这一点使得adb服务降权失败:当adb服务在执行setuid和setgid之前,我们让shell用户的进程数量达到上限,那么当它使用setuid来降权时,就会因为shell用户的进程数量满了而失败,使得adb继续保持root权限。

网上对于RageAgainstTheCage源码的分析也有不少,我这里就不详述了,只是画一些图方便大家理解,

流程图:






时序图:






最后,如果要封堵这个漏洞也非常简单,只需对那两个函数的返回值加以判断即可:




[cpp] view plaincopyprint?
01.int adb_main(int is_daemon)  
02.{  
03.      ......  
04.      property_get("ro.secure", value, "");  
05.      if (strcmp(value, "1") == 0) {  
06.            secure = 1;  
07.      ......  
08.      }  
09.     if (secure) {  
10.           ......  
11.           if(setgid(AID_SHELL) != 0){ 
12.      exit(1); 
13.           };  
14.           if(setuid(AID_SHELL) != 0){ 
15.      exit(1); 
16.           };  
17.           ......  
18.      }  
19. } 


猜你喜欢

转载自arm10504.iteye.com/blog/2068404
今日推荐