Android adb setuid提权漏洞由Sebastian Krahmer在2010年公布,并发布利用工具RageAgainstTheCage (rageagainstthecage-arm5.bin)。该工具被广泛用于SuperOneClick、z4root等root工具和Trojan.Android.Rootcager、DreamDroid等恶意代码中。
该工具支持Gingerbreak 2.2.X及更低版本的系统。
漏洞原理:
adb启动时最初拥有root权限,在运行过程中需要降成shell权限。如下所示,adb源码中对于setgid()和setuid()没有检查返回值,导致降权失败后继续运行。
于是利用linux对shell进程存在数量限制的特点,产生大量僵尸进程来造成降权失败,这样继续运行的adb进程就拥有root权限。
从漏洞修补后的adb源码中我们可以看出,增加了对返回值的检查。
漏洞修补前的adb源码:
int adb_main(int is_daemon) { ...... property_get("ro.secure", value, ""); if (strcmp(value, "1") == 0) { secure = 1; ...... } if (secure) { ...... setgid(AID_SHELL); setuid(AID_SHELL); ...... } }
漏洞修补后的adb源码:
int adb_main(int is_daemon) { ...... property_get("ro.secure", value, ""); if (strcmp(value, "1") == 0) { secure = 1; ...... } if (secure) { ...... if(setgid(AID_SHELL) != 0){ exit(1); }; if(setuid(AID_SHELL) != 0){ exit(1); }; ...... } }
The Android Exploid Crew小组后来发布了一份PoC代码:rageagainstthecage.c[1],我们对此进行分析。
各函数功能:
函数 die(23):反馈错误信息
函数 find_adb(29):在当前进程空间中查找adb进程,并返回其进程号
函数 restart_adb(51):杀死adb进程
函数 wait_for_root_adb(57):循环检测adb进程是否重启(此处kill(-1, 9); (68)不太理解,推测应该是结束所有僵尸进程),重启的adb进程应该就具有root权限
从main函数中我们可以看出漏洞利用具体方法:
main函数首先检测系统对shell进程数量是否存在上限(RLIMIT_NPROC),如果存在则查找adb进程。此处利用匿名管道(pepe[2])进行同步,fork()的父进程在read()处阻塞,而子进程无限循环fork()产生新的子进程,新的子进程直接exit(0)形成大量僵尸进程,占用shell进程号。直到达到进程号上限,fork()将失败,这时调用write()使得父进程解除阻塞重启adb进程。最后调用wait_for_root_adb等待新的adb启动即可(adb属于系统服务,如果终止会被系统自动重启)。
参考文献
[1]http://www.claudxiao.net/2011/04/android-adb-setuid/
[2]http://blog.csdn.net/andone_lsl/article/details/8661088(流程图可以帮助理解)