xingzhegu 2012-12-31
破解android的root权限的本质是:在系统中加入一个任何用户都可能用于登陆的su命令。或者说替换掉系统中的su程序,因为系统中的默认su程序需要验证实际用户权限,只有root和shell用户才有权运行系统默认的su程序,其他用户运行都会返回错误。而破解后的su将不检查实际用户权限,这样普通的用户也将可以运行su程序,也可以通过su程序将自己的权限提升。
root破解没有利用什么Linux内核漏洞(Linux内核不可能有这么大的漏洞存在),可以理解成root破解就是在你系统中植入“木马su”,说它是“木马”一点儿都不为过,假如恶意程序在系统中运行也可以通过su来提升自己的权限的这样的结果将会是灾难性的。所以一般情况下root过手机都会有一个SuperUser应用程序来让用户管理允许谁获得root权限,也算是给系统加了一层保险吧!
Android的应用程序入口肯定是Java程序。应用程序的启动者是由系统临时根据Androidmanifest.xml中定义的权限而创建的临时用户。而不像linux那样是使用登陆者的身份启动,从而使得进程具有登陆者的所有权限。这也是Android的安全机制之一。
新的权限机制也带来新的问题,Android给应用程序的权限是按功能来分,java虽然可以访问文件系统。但由于应用程序本身是临时用户启动,这个临时用户权限十分有限。因此诞生了<越狱/root机器>这样的产物。
其实root机器不是真正能让你的应用程序具有root权限。它原理就跟linux下的像sudo这样的命令。在系统的bin目录下放个su程序并且属主是root并有suid权限。则通过su执行的命令都具有Androidroot权限。
当然使用临时用户权限想把su拷贝的/system/bin目录并改属性并不是一件容易的事情。这里用到2个工具跟2个命令。
工具就是busybox。不熟悉的同学可以去网上google下。这个太有名了我就不多说了。把busybox拷贝到你有权限访问的目录然后给他赋予4755权限,你就可以用它做很多事了。
当然busybox不能提升权限,真正提升权限的是ratc这个程序,这个程序中一键root包里面可以找到,作用是rooting在adb的shell。
网上介绍Ratc的文章不多,它是rageagainstthecage的缩写。是真正的提升权限的破解程序。虽然我没看过源代码,但估计是利用adb源代码部分内容来实现的,
原理估计跟模拟器使用adbshell登陆可以获得rootshell差不多。(因为它运行需要adb连接才会成功)。
使用busybox前先运行ratc,这样运行busybox的UID将是0,也就是root。
首先把system目录改成可读性的:busyboxmount-oremount,rw/system,
当然你还不能改下面的文件,因为system下文件的所有者都不是你。但你可以偷梁换柱把system下的目录给换掉。
使用命令Busyboxmount-ttmpfsnone/system/xbin,呵呵这下xbin目录你随便写了。
将su跟busybox弄过去cp/data/data/xxx/su/system/xbin。然后赋权限chmod4755/system/xbin/su。
然后使目录生效busybox--install-s/system/xbin,别忘善后busyboxmount-oremount,ro/system去掉system可写。
这样只是临时的,只能用su跟busybox能执行一些原来系统没有权限执行的命令而已。当系统重启后/system/xbin又变为原来的文件。
真正要改系统的话需要自己写内核代码(相当于windows的驱动程序)。内核文件拥有所有权限。使用busybox命令insmod/data/data/xxx/xxx.ko装载内核文件,你想干嘛就可以干嘛了。
当然我们不是搞破解的没必要去改别人的机器,我们只是想让自己应用程序具有root权限而已。所以临时的su就可以了。
我们用c++写一个可执行文件。使用socket可以跟java的程序通讯。然后将需要使用root权限才能执行的代码放在c++程序里,然后java程序中创建新的su进程,
将c++程序带全路径作为参数1。启动后就可以通过socket调用c++函数去执行你想干的事了。最后程序执行完了别忘了善后busyboxumount/system/xbin。
最后说说要注意的事情,如果机器已经拥有Androidroot权限的话就不需要做这些事情了,但root过的机器都有装有个权限管理的程序。会弹出对话框。但这个程序管理能力有限,如果不想让他弹出的话。也许可以通过改su文件名来解决。有兴趣的同学不妨试试。
--------------------------------------------------------------------------------------------------------------------------------------------------------
破解手机Root权限是比较简单及安全的,破解Root权限的原理就是在手机的/system/bin/或/system/xbin/目录下放置一个可执行文件“su”,这是一个二进制文件,仅仅在系统中置入这个“su”文件是不会给手机的软件或硬件造成任何故障。
下面的代码是android系统原版的su中的部分代码,可以看出只允许getuid()为AID_ROOT和AID_SHELL的进程可以使用su进行登陆。
也就是说,Android系统默认的su程序只能root和shell可以用运行su,这个是安全的。如果把这个限制拿掉,就是root破解了,获取了root权限。
/*Untilwehavesomethingbetter,onlyrootandtheshellcanusesu.*/
myuid=getuid();
if(myuid!=AID_ROOT&&myuid!=AID_SHELL){
fprintf(stderr,"su:uid%dnotallowedtosu\n",myuid);
return1;
}
在Superuser(一个破解程序)这个android程序中的su不再有上面的一部分,这样任何进程都可以使用su进行登陆了。
还有一部分android程序要使用root权限可能的用法类似于(这个也是Superuser中的一部分代码):
Processprocess=Runtime.getRuntime().exec("su");
DataOutputStreamos=newDataOutputStream(process.getOutputStream());
os.writeBytes("mount-oremount,rw/dev/block/mtdblock3/system\n");
os.writeBytes("busyboxcp/data/data/com.koushikdutta.superuser/su/system/bin/su\n");
os.writeBytes("busyboxchown0:0/system/bin/su\n");
os.writeBytes("chmod4755/system/bin/su\n");
os.writeBytes("exit\n");
os.flush();
而在上面提到的Superuser和android原生的su源码中都有这部分代码:
if(setgid(gid)||setuid(uid)){
fprintf(stderr,"su:permissiondenied\n");
return1;
}
从出上面的分析可以认为破解android的root权限的实质是:在系统中加入一个任何用户都可能用于登陆的su命令。当然这首先要取得root权限才能做到。
在z4root这个android下的破解android的root权限的程序中有一个rageagainstthecage,这就是破解的关键。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
root破解过程的终极目标是替换掉系统中的su程序。但是要想替换掉系统中su程序本身就是需要root权限的,怎样在root破解过程中获得root权限,成为我们研究的重点了。
下面我们先清点一下我们需要破解系统情况,假设需要破解的Android系统具备如下条件:
1、可以通过adb连接到设备,一般意味着驱动程序已经安装。
2、但是adb获得用户权限是shell用户,而不是root。
要想理解root破解过程我们首先需要了解一下adb工具,SDK中包含adb(androiddebugbriage)工具,设备端有adbd服务程序后台运行,为开发机的adb程序提供服务,adbd的权限,决定了adb的权限。
具体用户可查看/system/core/adb下的源码,查看Android.mk你将会发现adb和adbd其实是一份代码,然后通过宏来编译。
查看adb.c的adb_main函数你将会发现adbd中有如下代码:
intadb_main(intis_daemon)
{
......
property_get("ro.secure",value,"");
if(strcmp(value,"1")==0){
//don'trunasrootifro.secureisset...
secure=1;
......
}
if(secure){
......
setgid(AID_SHELL);
setuid(AID_SHELL);
......
}
}
从中我们可以看到adbd会检测系统的ro.secure属性,如果该属性为1则将会把自己的用户权限降级成shell用户。一般设备出厂的时候在/default.prop文件中都会有:
ro.secure=1
这样将会使adbd启动的时候自动降级成shell用户。
然后我们再介绍一下adbd在什么时候启动的呢?答案是在init.rc中配置的系统服务,由init进程启动。我们查看init.rc中有如下内容:
#adbdiscontrolledbythepersist.service.adb.enablesystemproperty
serviceadbd/sbin/adbd
disabled
在init.rc中配置的系统服务启动的时候都是root权限(因为init进行是root权限,其子程序也是root)。由此我们可以知道在adbd程序在执行:
/*thenswitchuserandgroupto"shell"*/
setgid(AID_SHELL);
setuid(AID_SHELL);
代码之前都是root权限,只有执行这两句之后才变成shell权限的。这样我们就可以引出root破解过程中获得root权限的方法了,那就是让以上面setgid和setuid函数执行失败,也就是降级失败,那就继续在root权限下面运行了。
这其实利用了一个RageAgainstTheCage漏洞。这里面做一个简单说明:
1、出厂设置的ro.secure属性为1,则adbd也将运行在shell用户权限下;
2、adb工具创建的进程ratc也运行在shell用户权限下;
3、ratc一直创建子进程(ratc创建的子程序也将会运行在shell用户权限下),紧接着子程序退出,形成僵尸进程,占用shell用户的进程资源,直到到达shell用户的进程数为RLIMIT_NPROC的时候(包括adbd、ratc及其子程序),这是ratc将会创建子进程失败。
这时候杀掉adbd,adbd进程因为是Android系统服务,将会被Android系统自动重启,这时候ratc也在竞争产生子程序。在adbd程序执行上面setgid和setuid之前,ratc已经创建了一个新的子进程,那么shell用户的进程限额已经达到,则adbd进程执行setgid和setuid将会失败。根据代码我们发现失败之后adbd将会继续执行。
这样adbd进程将会运行在root权限下面了。
3、这时重新用adb连接设备,则adb将会运行在root权限下面了。
通过上面的介绍我们发现利用RageAgainstTheCage漏洞,可以使adbd获得root权限,也就是adb获得了root权限。
拿到root权限剩下的问题就好办了,复制破解之后的su程序到系统中,都是没有什么技术含量的事情了。
其实堵住adbd的这个漏洞其实也挺简单的:
/*thenswitchuserandgroupto"shell"*/
if(setgid(AID_SHELL)!=0){
exit(1);
}
if(setuid(AID_SHELL)!=0){
exit(1);
}
如果发现setgid和setuid函数执行失败,则adbd进程异常退出,就把这个漏洞给堵上了。
为什么这么多设备都没有堵上这个漏洞呢?我觉得是设备厂商的策略,虽然知道怎么封堵漏洞但是就是留着个后门给大家,让第三方给自己定制rom,提高自己系统的易用性。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
去年的Androidadbsetuid提权漏洞被用于各类root刷机,漏洞发现人SebastianKrahmer公布的利用工具RageAgainstTheCage(rageagainstthecage-arm5.bin)被用于z4root等提权工具、Trojan.Android.Rootcager等恶意代码之中。下面我们来分析这一漏洞的产生原因。
TheAndroidExploidCrew小组在后来发布了一份PoC代码:rageagainstthecage.c。从这份代码开始着手。
在main(:72)函数中,首先获取了RLIMIT_NPROC的值(:83),这个值是Linux内核中定义的每个用户可以运行的最大进程数。
然后,调用find_adb()函数(:94)来搜索Android系统中adb进程的PID,具体而言,该函数读取每个进程对应的文件的/proc/<pid>/cmdline,根据其是否等于”/sbin/adb”来判断是否adb进程。
接下来,fork了一个新的进程(:109),父进程退出,而子进程继续。接下来,在113行创建一个管道。
rageagainstthecage.c
代碼:
if(fork()>0)
exit(0);
setsid();
pipe(pepe);
重头戏发生在下面的122到138行,代码如下:
rageagainstthecage.c
代碼:if(fork()==0){
close(pepe[0]);
for(;;){
if((p=fork())==0){
exit(0);
}elseif(p<0){
if(new_pids){
printf("\n[+]Forked%dchilds.\n",pids);
new_pids=0;
write(pepe[1],&c,1);
close(pepe[1]);
}
}else{
++pids;
}
}
}
新建一个进程后,在子进程之中,exploit代码不断地fork()(:125),而新的子进程不断退出,从而产生大量的僵尸进程(占据shell用户的进程数)。
最终,进程数达到上限,fork()返回小于0,于是打印当前已经创建多少子进程,并向管道输入一个字符(:131)。
在这里,管道的作用是和(:122)fork出来的父进程同步,该进程在141行read这一管道,因而阻塞直至僵尸进程已经达到上限(:131)。
进一步的,exploit杀掉adb进程,并在系统检测到这一现象并重启一个adb之前,再一次fork(),将前一个adb留下的进程空位占据。
最后,在152行,exploit调用wait_for_root_adb(),等待系统重启一个adb,这个新建的adb就会具有root权限。
为什么在shell用户的进程数达到上限RLIMIT_NPROC以后,新建的adb会具有root权限?我们来看adb的源码。
在<android_src>/system/core/adb/adb.c的第918行,我们可以看到如下代码:
android_src/system/core/adb/adb.c
代碼:/*thenswitchuserandgroupto"shell"*/
if(setgid(AID_SHELL)!=0){
exit(1);
}
if(setuid(AID_SHELL)!=0){
exit(1);
}
这已经是漏洞修补以后的代码。在漏洞最初被发现时,代码如下:
android_src/system/core/adb/adb.c
代碼:/*thenswitchuserandgroupto"shell"*/
setgid(AID_SHELL);
setuid(AID_SHELL);
简而言之,原来没有检查setuid()函数的返回值。事实上,在此之前,adb.c中的代码都是以root权限运行,以完成部分初始化工作。
在这一行,通过调用setuid()将用户从root切换回shell,但setuid()在shell用户进程数达到上限RLIMIT_NPROC时,会失败,因此adb.c继续以root身份运行,而没有报错。
我们来看setuid()的man手册(man2setuid),其中有如下说明:
man2setuid
代碼:RETURNVALUE
Onsuccess,zeroisreturned.Onerror,-1isreturned,anderrnois
setappropriately.
ERRORS
EAGAINTheuiddoesnotmatchthecurrentuidanduidbringsprocess
overitsRLIMIT_NPROCresourcelimit.
可以看到,setuid是可能发生错误的,并且在uid的进程数超过RLIMIT_NPROC极限时,发生EAGAIN错误。
在android的源码中,setuid()定义于<android_src>/bionic/libc/unistd/setuid.c,实际上引用了一个外部符号__setuid,
这个符号在<android_src>/bionic/libc/arch_xxx/syscalls/__setuid.S中定义,最终是一个%eax=$__NR_setuid32,%ebx=uid的int0×80中断。
因为只是要分析原理,我们不再鏖战于Android,转而看向Linux内核。
在最新的kernel2.6中,setuid()位于kernel/sys.c的682行,其中,在697行,一切正常的情况下,它会调用set_user()来完成用户切换。
set_user()实现于同一文件的587行,其中一部分代码如下:
kernel/sys.c
代碼:if(atomic_read(&new_user->processes)>=rlimit(RLIMIT_NPROC)&&
new_user!=INIT_USER){
free_uid(new_user);
return-EAGAIN;
}
含义很明显,当目标用户的进程数达到上限,那系统就不能再将一个进程分配给它,因而返回-EAGEIN。然后再setuid()中,直接跳过后面的代码,而返回错误。
至此,整个漏洞的原理已经分析完毕。整理如下:
1、在Android的shell用户下,制造大量的僵尸进程,直至达到shell用户的进程数上限RLIMIT_NPROC;
2、kill当前系统中的adb进程,并再次占据其进程位置以保持达到上限;
3、系统会在一段时间后重启一个adb进程,该进程最初是root用户,在完成少许初始化工作后,调用setuid()切换至shell用户;
4、此时shell用户的进程数已经达到上限,所以setuid()失败,返回-1,并且用户更换没有完成,adb还是root权限;
5、adb没有检查setuid()的返回值,继续后续的工作,因此产生了一个具有root权限的adb进程,可以被用于与用户的下一步交互。
实际上,setuid在目标用户进程数达到RLIMIT_NPROC极限时返回错误,这一问题可能产生的安全隐患最早可以追溯到2000年。而在2006年,出现了真正利用这一编码问题的漏洞(CVE-2006-2607)。
因此,这并不是一个全新的漏洞。我们可以得出几点结论:
1、函数返回值一直是忽略的对象,因为getuid()永远不会失败,程序员可能会认为setuid()也不会失败——至少没有遇到过,因此忽略了对返回值的检查。检查一个系统函数是否调用失败是一个常识,但又是很麻烦的事,如果为了省事而忽略,问题就可能产生了。
2、Android下的安全问题,很多并非全新的,而且个人判断将来还会有大量漏洞、恶意代码产生于传统思路,而作用于新的平台。面对这一新的平台,我们是否能抢先于攻击者做好防范准备,是一个需要我们思考和实践的问题。