写这篇文章前,首先要感谢Simon_fu,他的两篇关于root权限的文章对于我的工作起到了非常大的帮助,这篇文章可以说是对他的文章的一个补充。Simon_fu的文章可以参考如下两个网页:
一般来说,Android下的应用程序可以“直接”得到的最大的权限为system,但是如果我们需要在程序中执行某些需要root权限的命令,如ifconfig 等,就需要 root权限了。按照Simon的文章中提到的,应用程序有以下两种办法临时获得root权限:
1) 实现一个init实现一个 Service ,来帮助 Android 应用程序执行 root 权限的命令。
2) 实现一个虚拟设备,这个设备帮助Android应用程序执行root 权限的命令。
第二种办法我这里没有尝试,暂时也不会。这里讲讲我在实现第一种办法的过程和遇到的一些问题。
1. 将我们要执行的命令写成脚本,或者可执行程序。
下面是我的脚本ifconfig_test.sh:
# !/system/bin/sh
ifconfig
注意: 脚本的第一行必须为 # !/system/bin/sh,否则无法执行,通过dmesg可以查看到信息内容为 cannot execve ./ifconfig_test.sh: Exec format error
也可以采用C/C++编写需要执行的命令或者程序,并在编译image的时候编译成可执行程序。
2. 在init.rc中注册service
Android 中的 service 需要在init.rc中注册,Init.rc 中定义的 Service将会被init进程创建,这样将可以获得 root 权限。当得到相应的通知(通过属性设置)后,init进程会启动该service 。
本文中注册的内容如下:
service ifconfig_test /system/etc/ifconfig_test.sh
oneshot
disabled
其中, oneshot表示程序退出后不再重新启动,disabled表示不在系统启动时启动。
注意:这里service name不能超过 16 个字符。我之前的service name由于定义的比较长,18个字符,设置属性通知 service 启动后查看 dmesg可以看到提示:init: no such service。查看/system/core/init/parser.c的源代码,在parse_service->valid_name函数中可以看到如下内容:if (strlen(name) > 16) { return 0; },证明service的名字的确不能超过 16 个字符。
3. 将Android应用程序提升为system 权限
既然应用程序可以通过启动service获得root 权限,那么岂不是很不安全。 Android 考虑到了这点,规定只有 system 权限的应用程序才能设置属性,通知 service 启动。关于提升 system权限的文章网上已有很多,这里就不再细说,可以参考如下两篇文章:
http://blog.csdn.net/liujian885/archive/2010/03/22/5404834.aspx
http://labs.chinamobile.com/mblog/532767_73183
4. 在应用程序中添加属性设置代码
前面已经提到,对于Android来说,应用程序通知init 启动 service是通过设置系统属性来完成的,具体为设置System系统属性“ctl.start” 为 “ifconfig_test”,这样Android系统将会帮我们运行 ifconfig_test 这个service了。
对该系统属性的设置有三种方法,分别对应三种不同的应用程序:
1) Java 代码
Android 在 Java 库中提供 System.getProperty 和 System.setProperty 方法, Java 程序可以通过他们来设置和获得属性。代码如下:
SystemProperties.set("ctl.start", "ifconfig_test");
上面的代码是通知Android执行ifconfig_test service,如果需要查询当前service 执行的状态,如是否执行完毕,可以通过如下代码查询:
ret = SystemProperties.get("init.svc. ifconfig_test ", "");
if(ret != null && ret.equals("stopped"))
{
return true;
}
2) JNI代码
当编写NDK的程序时,可以使用property_get和 property_set这两个API来获得和设置属性。使用这两个 API 必须要包含头文件 cutils/properties.h和链接libcutil库。
3) Shell脚本
Android 提供了命令行 setprop 和getprop来设置和获取属性,他们可以在脚本中被使用。
由于我的程序是在JNI中调用脚本,脚本中又执行ifconfig,因此我将设置属性的部分放在了脚本中完成,代码如下:
setprop ctl.start ifconfig_test
#wait for the service until it stops
ret=1
while [ $ret -ne 0 ]
do
getprop | grep "$ENABLE_MAPPER_SRV" | grep stopped
ret=$?
done
通过上面 4 个步骤,Android应用程序就获得了root 权限,更具体的说,是在执行我们需要执行的命令时临时获得了root 权限。
Android应用程序获得root权限
我在博文《Android程序的安全系统》中提到两种让root权限的办法。最近在网上发现很多朋友转载那篇文章,但是对那篇文章中提到的第一种方法怎样实现,不是很明白。本文将会以一个例子实现来演示怎样让一个Android应用程序获得root权限。
问题
我遇到的问题是我想在Java应用程序中动态mount一个NFS的系统,但是执行mount命令必须要要root权限才可以。一般情况下,在Android的Java层是不能获得root权限的。
思路
我在博文《Android程序的安全系统》中提到两种思路:
1、实现一个init实现一个Service,来帮助Android应用程序执行root权限的命令。
2、实现一个虚拟设备,这个设备帮助Android应用程序执行root权限的命令。
本文将会选择第一种来解决Android应用程序mount NFS文件系统的问题。
Init.rc Service
在Android系统init.rc中定义很多Service,具体定义格式可以参考《Android Platform Developer’s Guide》中的“Android Init Language”。Init.rc中定义的Service将会被Init进程创建,这样将可以获得root权限。
现在问题是Android应用程序怎样启动让init进程知道我们想运行那个进程呢?答案是设置系统属性“ctl.start”,把“ctl.start”设置为你要运行的Service,假设为“xxx”,Android系统将会帮你运行“ctl.start”系统属性中指定的Service。那么运行结果init进程将会将会写入命名为“init.svc.+Service名称”的属性中,也就是“init.svc.xxx”属性,应用程序可以参考查阅这个值来确定Service执行的情况。想更深入了解Android property系统可以参考博文《(翻译)Android属性系统》。
Android property权限
难道Android属性“ctl.start”是所有进程都可以设置的吗?那世界不就乱套了,谁都可以可以执行init.rc中Service了,查看property_service.c中的源码,设置Android系统属性的函数为handle_property_set_fd:
1: void handle_property_set_fd(int fd)
2: {
3: ......
4: switch(msg.cmd) {
5: case PROP_MSG_SETPROP:
6: msg.name[PROP_NAME_MAX-1] = 0;
7: msg.value[PROP_VALUE_MAX-1] = 0;
8:
9: if(memcmp(msg.name,"ctl.",4) == 0) {
10: if (check_control_perms(msg.value, cr.uid, cr.gid)) {
11: handle_control_message((char*) msg.name + 4, (char*) msg.value);
12: } else {
13: ERROR("sys_prop: Unable to %s service ctl [%s] uid: %d pid:%d\n",
14: msg.name + 4, msg.value, cr.uid, cr.pid);
15: }
16: }
17: ......
18: }
19: }
从源码中我们发现如果设置“ctl.”开头的Android系统property,将会调用check_control_perms函数来检查调用者的权限,其定义如下:
1: static int check_control_perms(const char *name, int uid, int gid) {
2: int i;
3: if (uid == AID_SYSTEM || uid == AID_ROOT)
4: return 1;
5:
6: /* Search the ACL */
7: for (i = 0; control_perms[i].service; i++) {
8: if (strcmp(control_perms[i].service, name) == 0) {
9: if ((uid && control_perms[i].uid == uid) ||
10: (gid && control_perms[i].gid == gid)) {
11: return 1;
12: }
13: }
14: }
15: return 0;
16: }
我们发现root权限和system权限的应用程序将会授权修改“ctl.”开头的Android系统属性。否则将会检查control_perms全局变量中的定义权限和Service。
如果想更深入的了解Android Init进程和Android Property的权限控制,请参考《Android Permission》。
实例
通过上面的介绍我们基本已经有思路了,下面以上面提出的mount nfs文件系统为例说明:
1、首先定义一个执行mount的脚本,我把它位于/system/etc/mount_nfs.sh,定义如下:
1: #!/system/bin/sh
2:
3: /system/bin/busybox mount -o rw,nolock -t nfs 192.168.1.6:/nfs_srv /data/mnt
不要忘了把它加上可执行权限。
2、在init.rc中加入一个Service定义,定义如下:
1: service mount_nfs /system/etc/mount_nfs.sh
2: oneshot
3: disabled
3、让自己的应用程序获得system权限,博文《Android程序的安全系统》中提到了怎样获得system权限,获得system用户权限,需要以下步骤:
(1). 在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。
(2). 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行
(3). 使用mm命令来编译,生成的apk就有修改系统时间的权限了。
4、在自己应用程序中设置System系统属性“ctl.start”为“mount_nfs”,这样Android系统将会帮我们运行mount_nfs系统属性了。这里需要强调的是不能够调用System.getProperty,这个函数只是修改JVM中的系统属性。而不能修改Android的系统属性。可以调用android.os.SystemProperties(Android 2.1 Eclair系统可以调用这个API),如果你的Android版本不能调用这个类,只能通过JNI,调用C/C++层的API property_get和property_set函数了。如果想详细了解请参考《(翻译)Android属性系统》。代码如下:
1: SystemProperties.set("ctl.start", "mount_nfs");
5、最后在自己应用程序中,读取“init.svc.mount_nfs”Android系统Property,检查执行结果。代码如下:
1: while(true)
2: {
3: mount_rt = SystemProperties.get("init.svc.mount_nfs", "");
4: if(mount_rt != null && mount_rt.equals("stopped"))
5: {
6: return true;
7: }
8:
9: try
10: {
11: Thread.sleep(1000);
12: }catch(Exception ex){
13: Log.e(TAG, "Exception: " + ex.getMessage());
14: }
15: }
init进程维护一个service的队列,所以我们需要轮训来查询service的执行结果。
通过上面的这些步骤,Android应用程序就能够调用init.rc中定义的Service了。这样你的Android应用程序也就获得了root权限。
总结
通过上文可以看出,在Android获得root权限还是需要一些前提的,比如:
1、必须是Android系统开发人员,否则你无法修改init.rc等文件。 2、你的应用程序必须要获得system权限。
这样可以防止root权限被应用程序无限制的使用,最终危及Android系统安全。
希望本文对你能有所帮助,如果有错误之处敬请指正。
COMMENTS
52 条评论
bevis
2011-01-20 14:51:20
谢谢simon,每次定期的来你这里看看,总会学到很多知识。前几天就刚好遇到你说的这个问题,今天来这里看了一下,问题看来可以解决了,呵呵,谢谢!
[回复]
Simon_fu 回复:
一月 20th, 2011 at 15:52
欢迎你多来关注Simon,也欢迎你对Simon的文章多提宝贵意见!大家共同进步!
[回复]
flye03
2011-01-21 16:12:07
后来我更改service 为
service mtest /system/bin/logwrapper /system/etc/a.sh
后程序就能正确运行了,不知道是什么原因。总体上是通过了,感谢simon的分享。
[回复]
tang
2011-02-24 13:44:13
Simon,你真棒
[回复]
Simon_fu 回复:
二月 24th, 2011 at 16:30
欢迎大家多指正!
共同进步~!
[回复]
gqdy365
2011-02-24 18:11:04
我有个问题想请教一下楼主,我在做fm模块时遇到了一个权限问题,驱动层生成的文件节点在dev目录下面,上层打开它时说没有权限,我通过串口修改权限后是可以打开的,我网上查了一下,有人说是在init.rc里面做初始化修改,可以指点一下吗?
[回复]
Simon_fu 回复:
二月 24th, 2011 at 21:04
确实可以在init.rc中做,init.rc提供了chown和chmod命令,你可以参考system/core/init/readme.txt文件,里面对init.rc的命令进行了详细的描述,网上也有人已经翻译成了中文。
[回复]
gqdy365 回复:
二月 25th, 2011 at 08:58
谢谢你,支持你的博客!
[回复]
billyc
2011-03-10 03:06:19
Thank you for this great article. The only one article talk about get app root access in an unrooted Android device.
Great Job! and thank you again.
[回复]
匿名
2011-03-14 11:08:10
总结
通过上文可以看出,在Android获得root权限还是需要一些前提的,比如:
1、必须是Android系统开发人员,否则你无法修改init.rc等文件。 2、你的应用程序必须要获得system权限。
这样可以防止root权限被应用程序无限制的使用,最终危及Android系统安全。
不等于没说么,如何是Android系统开发人员的话,难道他就没有root权限么? 没有Root权限,他还叫系统开发人员么?
最核心的问题是,大家都想知道,一般的应用程序如何能够请求到root权限,我目前system权限已经有了,就是没有root权限,因为我不是系统开发人员。
[回复]
Simon_fu 回复:
三月 14th, 2011 at 20:22
你不能修改init.rc呢?
如果能修改就可以了~!
Android系统设计目标就是不把root暴露给应用程序的,需要root权限的操作都已经封装成Service了,当然我的实现也是这样的。
如果你已经获得了system权限,那么你可以调用系统中定义的各种service,由各种service来执行需要root权限的操作。如果你要的操作没有对应的service,那么这就是你的系统提供商不想暴露给你。你唯一能做的就是破解它,来获得root权限了。
Android系统开发人员有root权限,但是他们要把需要root权限的操作暴露给上层的应用程序也是需要考虑的一个方面。也是Android系统安全的一个考虑的方面。
[回复]
匿名
2011-03-20 12:36:49
simon您好!
您提到“实现一个虚拟设备,这个设备帮助Android应用程序执行root权限的命令”
我现在已经写了一个相应驱动,但不知道android app如何跟底层的linux驱动通信,一直找不到头绪,能指导下我吗?
万分感谢
[回复]
Simon_fu 回复:
三月 20th, 2011 at 14:25
实现一个字符设备驱动,然后app打开这个设备驱动文件,然后向里面写入需要执行的命令,由驱动程序代理执行相应的命令。
[回复]
匿名 回复:
三月 21st, 2011 at 13:18
谢谢您!
祝博主天天开心!
[回复]
匿名
2011-04-07 17:09:36
simon您好!
看了您的博客,收益很大,想问问您两个问题
1,您说的使用虚拟字符驱动的方法,在方法里怎样调用eth0让它开启,设置ip等呀,可以提供些函数嘛?
2,使用服务的方法,怎样把参数传进去呀?运用一个脚本是ok的。
希望指导下,万分感谢
[回复]
Simon_fu 回复:
四月 7th, 2011 at 17:54
首先,实现字符设备的方法,我是这样做的,就和打开普通文件一样打开虚拟字符设备,然后把需要执行的命令一次性写入该设备文件,设备驱动就开始代理执行这些命令;你可以向做驱动的同事请教这个问题。
其次:服务的方法,脚本应该也是可以处理参数的,当然你也可以通过C/C++编写一个程序来完成服务也是一样的。你可以查阅一下脚本的实现,另外init服务也是可以加参数的,请参考Android init readme.
[回复]
匿名 回复:
四月 8th, 2011 at 08:34
很谢谢您的及时回复
驱动也是我自己写的,现在问题就在于设备怎样去执行那些命令,驱动中没有system函数的调用,ifc_up也用不了,如何把ifconfig这个文件加入的话好像就麻烦了点,您说的“设备驱动就开始代理执行这些命令”是怎样代理的,能指点下嘛,非常感谢
[回复]
Simon_fu 回复:
四月 8th, 2011 at 09:20
请参考一下这个帖子:
http://www.unixresources.net/linux/clf/linuxK/archive/00/00/57/83/578302.html
另外,虚拟设备方案不是Android平台下面常规方案,主要涉及到安全性;只有万不得已的时候再用。优先选用init服务的方案会比较好。
[回复]
匿名 回复:
四月 8th, 2011 at 11:12
谢谢您如此热心。
现在就是在考虑init服务写的那个socket如何能从用户手中获得参数,而不是在init中配置参数。
非常感谢您的回答,祝您工作顺利,身体健康
[回复]
byfrancis
2011-04-13 10:23:08
Simon您好,看了你的博文感觉受益匪浅,按照我的理解,文中的方法好像没有不能让用户“一键获取root的权限”,或者是让应用程序自动获取root权限。我知道网上有个应用程序Z4root,可以让用户一键获取root权限,但我不知道是怎么实现的。如果是我对您的方法没有理解,或者是您有一键获取root权限的方法,能指点一下吗?不胜感激!
[回复]
Simon_fu 回复:
四月 13th, 2011 at 10:34
这个就是传说中root破解,我没有研究过这种破解方式,没有发言权。不好意思!
[回复]
byfrancis 回复:
四月 13th, 2011 at 22:29
非常感谢,期待您后续的android博文
[回复]
chaos
2011-04-15 08:03:01
simon牛,问你一个比较傻的问题啊
像RE之类的文件甚至能有重新把根目录挂载为RW的权限
这种权限也是通过这种方法实现的吗
还是说已经root的手机应用程序即可以通过
process = Runtime.getRuntime().exec(“su”);
这种语句来获取root权限??
[回复]
Simon_fu 回复:
四月 15th, 2011 at 08:54
我在网上看到很多人都是这样获得root权限的,但是这必须要是rooted过的手机,否则没戏!所以这种方法不通用。rooted手机本身也是利用了系统漏洞,如果那一天把漏洞堵上,也就没戏了!
[回复]
chaos 回复:
四月 20th, 2011 at 10:56
谢谢simon牛
我看了下android 那些terminal模拟器的代码,自己编译了,发现他们在没有root权限的机器上是没有办法执行su的,也看了下superuser的代码,它的思路是修改机器上的su程序,替换成自己编译的su程序,大致明白了通过process = Runtime.getRuntime().exec(“su”);怎么获取root权限的。
ps:
有一个小问题,我看了你上文提到的请参考《Android Permission》。里面我尝试着对init.rc文件进行修改,但是每次修改后重启机器init.rc文件都恢复成为修改前的状态了,请问你有修改过该文件吗?
[回复]
Simon_fu 回复:
四月 20th, 2011 at 14:10
因为你的根分区是是只读的,试试adb remount行不行?
[回复]
chaos 回复:
四月 20th, 2011 at 18:37
对 我是root权限 mount -o rw,remount -t rootfs /
[回复]
匿名 回复:
四月 22nd, 2011 at 00:59
init.rc 不是在ramdisk裡面嗎?ramdisk運行在內存裡面,
每次重開機當然會恢復之前的狀態。
[回复]
chaos 回复:
四月 24th, 2011 at 01:31
谢谢了 那init.rc未启动时具体在rom的什么文件里面呢?
是不是在启动之后再映射到内存内吗
如果要修改是不是得修改rom然后重新刷机才可以成功??
[回复]
Simon_fu 回复:
四月 24th, 2011 at 21:14
呵呵!我只是研究了这个技术,刷机这种事儿我可没干过!
这个帮不了你啊!
你可以到刷机网站上面请教这个问题。
[回复]
chaos 回复:
四月 24th, 2011 at 19:16
那在为映射到内存之前init.rc对应哪个文件呢?
是否修改init.rc必须先修改rom
然后重新刷机才能生效??
[回复]
pcsuite
2011-04-16 12:15:53
楼主,你好,我看了你很多篇文章,写的都很好,而且对我的帮助也很大,在此表示感谢。
[回复]
dxz
2011-04-22 18:18:20
你好,我要是想用我的应用在system目录下创建一个文件,用你说的那方法好像不可行吧。能不能给个思路。谢谢
[回复]
Simon_fu 回复:
四月 22nd, 2011 at 22:46
你遇到什么问题了吗?
我觉得是可以做到的!因为已经有root权限了,在系统中创建文件没有理由不成功的。
system分区是只读的,你可能需要remount一下吧。
[回复]
dxz 回复:
四月 23rd, 2011 at 08:46
您好,我是初学者,您说的那个服务该怎么写呢 ?我还是没明白那服务是怎么回事?为什么掉声明一个服务就可以应用就可以获取root权限呢 ?还是应用只能调用声明的那个服务里的方法?
[回复]
chaos
2011-04-24 01:34:49
simon牛
最近我打算详细阅读以下android框架的源代码
ActivityManagerService.java
对ActivityManagerService的大致流程有所了解了
但是对于细节很难看懂
能不能介绍一下你阅读android源码的技巧吗
或者在国外有没有对android相关源码剖析的文章或blog吗?
尤其是对ActivityManagerService.java部分的??
非常感谢
[回复]
Simon_fu 回复:
四月 24th, 2011 at 21:16
说实话,上万行的代码我也不看!
只是说自己需要的时候,顺藤摸瓜摸到这里而已!
另外既然是service,熟悉了Android service对你理解它也很有帮助。
[回复]
chaos
2011-04-24 19:20:04
simon牛,请教一个问题
请问一下能不能指点一下阅读android框架源代码的技巧啊
比如我最近在阅读ActivityManagerService.java 光这一个模块就一万多行的代码
大体知道是一个什么它流程 但细节深究实在是很乱啊 看不出头绪
或者外国有没有相关剖析android源代码的blog或文章吗??
[回复]
aplikacje na telefon
2011-04-30 08:58:52
The most comprehensive and very well thought out write up I have found on this subject on the net. Keep on writing, I will keep on coming by to read your new content. This is my fourth time coming by your blog.
[回复]
soros
2011-05-25 10:04:59
谢谢楼主共享,不知道楼主是否研究过Runtime.exec() 方法执行shell 指令呢? 这样是不是也能够获取到root 执行权限呢? 我正在使用这种方法,但是却不能够正常执行shell 脚本。
[回复]
Simon_fu 回复:
五月 28th, 2011 at 18:11
可以肯定的告诉你,绝对不可能拥有root权限。因为只有在java层才能调用Runtime.exec(),这样的进程不可能是root权限的进程,最高是system权限。
[回复]
abc
2011-05-28 13:57:52
理解错,谢谢,问题已经解决了
[回复]
abc
2011-06-03 09:55:44
解决了,是线程并发问题。
[回复]
匿名
2011-07-10 16:35:53
simon_fu;你好,我想请问一下,我要通过jni方式去操作/dev目录下的设备文件,我看您的博文说只要获得system权限就可以操作设备了,但是我获得了system权限还是不能访问设备文件,请问访问设备文件ap必须要具备root权限吗?
[回复]
Simon_fu 回复:
九月 14th, 2011 at 14:07
这个就要看你的设备驱动要求的权限了!
有的设备驱动必须要root才可以!
需要驱动哦你工程师商议!
[回复]
lufengdie
2011-08-01 15:01:21
“首先定义一个执行mount的脚本,我把它位于/system/etc/mount_nfs.sh”
这一步不能操作啊。
请教楼主、
[回复]
Simon_fu 回复:
九月 14th, 2011 at 13:58
说明你没有设备的root权限,或者你没有remount system分区。
[回复]
匿名
2011-08-19 22:34:30
你好simon,我最近遇到一个问题,我的apk要执行一些需要root权限的命令.看了你介绍的这种方法(第一种),可是这种方法怎么取得命令执行的输出呢?
[回复]
Simon_fu 回复:
九月 14th, 2011 at 13:55
把命令行重定向到文件,然后执行结束之后,读取文件内容。
[回复]
vincent.yan
2011-08-23 19:40:10
simon牛,看了你的几篇文章,收益匪浅
但是我现在遇到类似问题,希望能给予指导。
首先我要实现的是一个android应用,但是我想要获取到root权限。(不是Android系统开发人员),这个情况该怎么做?是否用第二种方法可以实现
或者你有什么好方法让应用在非root权限下,去抓屏呢?
[回复]
Simon_fu 回复:
九月 14th, 2011 at 13:54
抓屏?
我觉得应该是要用到framebuffer吧!
没有root权限我觉得难!
不好意思!
[回复]
Simon_fu
2011-09-14 13:59:41
研究一下init的帮助文档,里面解释了init service参数传递问题。
[回复]