【注:CSDN博客文章随着知识面的增加,会不断重构。微信公众号已发的文章,重构CSDN原文章后不会再做修改,更多精彩内容,请关注橙留香博客导读:https://orangey.blog.csdn.net/article/details/123788957】
run-as是Android系统中的一个小应用,可以辅助我们进行Android程序的开发。
功能:run-as命令,以root身份运行命令,可以在未root的情况下查看某个(debug模式的)应用的内部信息(沙盒文件夹),前提条件是应用需为debug模式,即AndroidManifest.xml文件中,android:debugable需要为true
在开发Android应用的时候,有的时候希望能获取到保存到手机盘应用私有目录/data/data/com.mwr.dz下的数据来验证开发的应用是否达到预期结果,或者获取写到私有目录的log日志文件来进行debug工作。
在没有root过的手机中,我们是无法直接使用adb shell来获取到/data/data/com.mwr.dz下的数据,此时的用户权限是无法查看很多信息的,包括各个应用的沙盒,而run-as就向开发者提供了未root情况下访问沙盒信息的权限,执行run-as + 包名,就可以直接以root权限进入该应用的沙盒中查看包括数据库、xml、各种信息文件
为什么命令可以查看到Android应用私有数据?
run-as程序就是通过s权限使得普通用户可以在满足条件(应用为debug模式时、操作目录仅在应用沙盒下)时具备root权限
除了提权以外,s权限还可以用来降权,root用户可更改文件的拥有者,以及通过s权限任意更改程序执行时的权限,在Linux中所有进程都是由zygote进程fork出来的,如果不降权的话则所有进程都与父进程相同、具备了root权限,这显然是不安全的,因此zygote通过forkAndSpecializeCommon函数来进行降权处理,使得子进程具备可控的权限
注:
1.使用run-as后,是无法使用cp命令将应用数据拷贝到sd卡的,没有权限
2.apk已签名,并且设置了android:debuggable="false",将无法使用run-as命令
3.无法使用run-as来直接获取应用数据的话,需在自己的应用里实现读取应用数据到sd卡的功能,应用本身对自己的数据是有访问权限的
4./data/data/com.packagename/lib目录不需要执行run-as就拥有访问权限,为apk间共享so提供了便利
Java中的调试系统jdb,其实Android中的调试系统是gdb,通过gdb和gdbserver来调试App。具体来说,就是gdbserver通过ptrace附加到目标App进程去,然后gdb再通过socket或者pipe来链接gdbserver,并且向它发出命令来对App进程进行调试。需要注意以下关键点:
每一个需要调试的apk在打包的时候都会带上一个gdbserver,因为手机上面不带有gdbserver工具。gdbserver用来通过ptrace附加到要调度的App进程去。
ptrace的调用。一般来说,只有root权限的进程可以调用。例如,如果想通过ptrace向目标进程注入一个so,需要在root过的手机上向su申请root权限。但是,这不是绝对的。如果一个进程与目标进程的uid是相同的,那么该进程就具有调用ptrace的权限。gdbserver在调试一个App之前,先通过ptrace_attach来附加到该App进程去,ptrace_attach在执行实际操作之后,会调用__ptrace_may_access来检查调用进程的权限,如果调用进程与目标进程具有相同的uid和gid,那么权限检查通过。否则的话,就要求调用者进程具有执行ptrace的能力,这是通过另外一个函数ptrace_has_cap来检查的。如果调用进程的uid是root,那么ptrace_has_cap一定会检查通过。当然,通过了上述两个权限检查之后,还要接受内核安全模块的检查,这就不是通过uid或者capability这一套机制来控制的了
如何让gdbserver进程的uid与要调试的App进程的uid一样?
在没有root过的手机上要想获得root权限是不可能的,因此只能选择以目标进程相同的uid运行这个方法,可使用run-as工具
众所周知,Android P 引入了针对非 SDK 接口(俗称为隐藏API)的使用限制,这是继 Android N上针对 NDK 中私有库的链接限制之后的又一次重大调整。从今以后,不论是native层的NDK还是 Java层的SDK,我们只能使用Google提供的、公开的标准接口。
Android P 和 Android Q:https://blog.csdn.net/github_34402358/article/details/94399343
用一个例子来详细介绍Android中如何降权,这个例子是模拟一个系统的API,但是这个API只允许system用户调用,代码如下:
在Android平台执行Java程序需要依赖dalvik虚拟机,因此需要将jar包转换为对应的字节码文件,操作步骤如下:
1、生成java jar包 同Pc执行
java -jar E:\test.jar
2、java jar包转dex包,执行如下命令
dx --dex --output=test.dex E:\test.jar
3、连接Android手机,并将dex执行程序推入Android设备路径下:
adb push E:\test.dex /data/local/tmp/
打开 IDEA 软件,点击界面上的 Create New Project
出现以下界面,选中 Java,然后选择 JDK,最后点击 Next,进行下一步(我的是 jdk1.8)
这里是选择生成项目时是否创建 Java 文件,勾选上 Java Hello World 后会生成一个默认的 Hello world 文件,点击 Next 进行下一步
给项目命名,默认是 untiled,自己填个名字吧,最后点击 finish
项目创建完成,创建 Java 文件:点击 src——>new——>package,创建一个文件包,并给包命名,与 Eclipse 的包类似
在包下面创建 Java 类文件,点击包名——>New——>Java Class;给类文件命名
进入手机shell
adb shell
进入sdcard目录
cd sdcard
设置将要执行的jar包的classpass,不设置将无法执行
export CLASSPATH=/sdcard/hello.jar
执行jar包
app_process hello.jar com.lyh.hello.Hello aaa bbb ccc
创建一个IDA 来编写Android 中的程序
run-as 命令可以获取debug应用的私有数据,在调试debug包的时候,可以使用该命令将用户切换到系统分配给debug应用的用户上,之后就可以访问其私有数据
adb shell
whoami
run-as /data/data/com.mwr.dz
whoami
注:在默认的shell环境下,用户身份是root(假设当前是普通用户,无法查看应用的私有数据),运行 run-as 后,身份切换了 u0_a40, 这个用户其实是测试应用的uid,也可以自由的访问其私有目录
Android中每一个应用都有一个uid,可以通过以下方式获取:
PS命令:
ps | grep com.mwr.dz
每个Android应用程序的u0_axx都是不一样的,同时uid是从10000开始,u0_a后面的数字加上10000所得的值,既是uid,这个和测试应用的输出也非常的吻合,即u0_a40 和 10040。
注:0_a后面的数字就是该应用的UID值减去FIRST_APPLICATION_UID所得的值,40 + FIRST_APPLICATION_UID = 10040
android2.3.3_r1中run-as.c中主要代码:
/* 没有传入包名则退出 */if (argc < 2)usage();/* 非'shell'用户或'root'用户则退出 */myuid = getuid();if (myuid != AID_SHELL && myuid != AID_ROOT) {
panic("only 'shell' or 'root' users can run this program\n");}/* 根据传入包名获取应用信息,失败则退出 */pkgname = argv[1];if (get_package_info(pkgname, &info) < 0) {
panic("Package '%s' is unknown\n", pkgname);
return 1;}/* reject system packages */if (info.uid < AID_APP) {
panic("Package '%s' is not an application\n", pkgname);
return 1;}/* 如果设置了android:debuggable="false",则退出 */if (!info.isDebuggable) {
panic("Package '%s' is not debuggable\n", pkgname);
return 1;}/* 检查/data/data/com.packagename目录是否可用 */if (check_data_path(info.dataDir, info.uid) < 0) {
panic("Package '%s' has corrupt installation\n", pkgname);
return 1;}/* 切换当前工作目录为/data/data/com.packagename,失败则退出 */{
int ret;
do {
ret = chdir(info.dataDir);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {panic("Could not cd to package's data directory: %s\n", strerror(errno));
return 1;
}}/* Ensure that we change all real/effective/saved IDs at the
* same time to avoid nasty surprises.
*//* 切换真实/有效/saved 用户ID和组ID*/uid = gid = info.uid;if(setresgid(gid,gid,gid) || setresuid(uid,uid,uid)) {panic("Permission denied\n");
return 1;}/* 用户在包名参数后指定了要执行的命令,就执行这个命令 */if (argc >= 3 ) {
if (execvp(argv[2], argv+2) < 0) {
panic("exec failed for %s Error:%s\n", argv[2], strerror(errno));
return -errno;
}}/* 用户没有传入要执行的命令,执行默认的shell命令子进程,此时可以执行exit命令退出 */execlp("/system/bin/sh", "sh", NULL);panic("exec failed\n");return 1;
9 8 7 6 5 4 3 2 1 0- r w x r - x r - x
第9位表示文件类型,可以为p、d、l、s、c、b和-:
p表示命名管道文件
d表示目录文件
l表示符号连接文件
s表示socket文件
c表示字符设备文件
b表示块设备文件
-表示普通文件
第8~6位、5~3位、2~0位分别表示文件所有者的权限、同组用户的权限、其他用户的权限,其形式为rwx:
r表示可读,可以读出文件的内容,对应的数字是4
w表示可写,可以修改文件的内容,对应的数字是2
x表示可执行,可运行这个程序,对应的数字是1
执行的话,分别表现在所有者或同组用户权限的可执行位上,例如:
1)-rwsr-xr-x 表示SUID和所有者权限中可执行位被设置
2)-rwsr--r-- 表示SUID被设置,但所有者权限中可执行位没有被设置
3)-rwxr-sr-x 表示SGID和同组用户权限中可执行位被设置
4)-rw-r-sr-- 表示SGID被设置,但同组用户权限中可执行位没有被设置
Android系统进程Zygote启动过程的源代码分析:http://blog.csdn.net/luoshengyang/article/details/6768304
Android的APK都是运行在独立的应用程序进程里面的,Android中的所有的应用进程和SystemServer进程都是Zygote进程fork出来的。Zygote进程又是由init进程fork出来的,并且它被init进程fork出来后,没有被setuid降权,也就是它的uid仍然是root。
应用程序进程被Zygote进程fork出来的时候,它的UID也应当是root。但是,它们的UID会被setuid修改为所加载的APK被分配的UID。
Android 2.2 以及之前版本的Zygote没有对降权时setuid调用的返回值进行检查。同样,在耗尽目标程序uid的最大进程数之后,Zygote就无法降低它的权限,然后就以root权限启动应用了
虽然Zygote进程相当于Android系统的根进程,但它也是由Linux系统的init进程启动的。各个进程的先后顺序为:
init进程 –-> Zygote进程 -–> SystemServer进程 -–>应用进程
所有Android应用进程都是zygote fork出来的,新fork出来的应用进程还保持着root权限,这显然是不被允许的,所以这个fork出来的子进程的权限需要被降级,本文说的就是Android源码在什么地方执行了权限降级的操作
zygote降权处理
Zygote启动流程:
1)初始化DDMS
2)注册Zygote进程的Socket
3)加载class、resource、OpenGL、WebView等各种资源
4)fork出SystemServer进程
5)启动SystemServer进程
6)调用runSelectLoop()一直监听Socket信息
7)收到创建应用程序Socket消息,调用ZygoteConnection#runOnce()。在runOnce()中调用Zygote#forkAndSpecialize()创建应用进程
8)启动应用进程
su工具原理
Android手机的root原理:一个普通的进程通过执行su,从而获得一个具有root权限的进程
su所做的事情其实很简单,它再fork另外一个子进程来做真正的事情,也就是在执行su的时候后面所跟的那些参数。由于运行su的进程的uid是root,因此由它fork出来的子进程的uid也是root,于是子进程也可以达到跟root一样的权限操作稳健。不过,用来root手机的su还会配合另外一个称为superuser的App来使用。su在fork子进程来做真正的事情之前,会先启动superuser,询问用户是否允许fork一个uid是root的子进程,对root权限进行控制,避免被恶意应用偷偷地使用
chmod修改setuid和setgid場景:run-as命令
在Android中也有类似的场景,例如run-as命令本生的uid是root,gid是shell,输入 run-as 命令的用户就会立马升级到root用户,root用户可以进入到任何应用的data/data/目录下面
chmod 6755 run-as
ll /system/bin/run-as
参考链接:
https://blog.csdn.net/whklhhhh/article/details/81177455
https://blog.csdn.net/moonshine2016/article/details/53422082
https://daemon369.github.io/android/2013/09/03/use-run-as-get-android-private-data
https://www.codeboy.me/2019/09/10/android-run-as/
https://blog.csdn.net/qq_17250009/article/details/52061171
http://www.520monkey.com/archives/607
推荐阅读