Android下的权限管理
ROOT是我们对于Android手机孜孜不倦的追求,不过ROOT其实也是个比较复杂的过程。抛去复杂的构思,从本质来看,就是通过一系列非常规的手段,比如溢出啊,或者一些系统级的BUG构造出获得“#”用户的权限,然后我们在获得ROOT权限后,为了保持这个权限,还需要做一些工作,即“权限管理”。
手机在获得ROOT之后,一般会往手机中拷贝入两个文件,以保证后续的程序在申请ROOT权限的时候,能够方便的直接获取。一个是ROOT BIN:su-binary;另外一个是SuperUser.apk,包名是com.noshufou.android.su;据说这是个国人写的,虽然漏洞貌似不是它找的。
su程序并不能直接运行,我是指如果把它想成是Linux那样,拷贝一个su-binary到其它手机的sdcard或者甚至是系统目录,就能让手机在输入su命令下直接进入ROOT用户试试不行的,至少作者以及修改者不是这么一个设计。su的main函数代码主要部分如下所示:
if (from_init(&su_from) < 0) { deny(); } orig_umask = umask(027); if (su_from.uid == AID_ROOT || su_from.uid == AID_SHELL) allow(shell, orig_umask); if (stat(REQUESTOR_DATA_PATH, &st) < 0) { PLOGE("stat"); deny(); } if (st.st_gid != st.st_uid) { LOGE("Bad uid/gid %d/%d for Superuser Requestor application", (int)st.st_uid, (int)st.st_gid); deny(); } if (mkdir(REQUESTOR_CACHE_PATH, 0770) >= 0) { chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid); } setgroups(0, NULL); setegid(st.st_gid); seteuid(st.st_uid); LOGE("sudb - Opening database"); db = database_init(); if (!db) { LOGE("sudb - Could not open database, prompt user"); // if the database could not be opened, we can assume we need to // prompt the user dballow = DB_INTERACTIVE; } else { LOGE("sudb - Database opened"); dballow = database_check(db, &su_from, &su_to); // Close the database, we're done with it. If it stays open, // it will cause problems sqlite3_close(db); db = NULL; LOGE("sudb - Database closed"); } switch (dballow) { case DB_DENY: deny(); case DB_ALLOW: allow(shell, orig_umask); case DB_INTERACTIVE: break; default: deny(); } socket_serv_fd = socket_create_temp(); if (socket_serv_fd < 0) { deny(); } signal(SIGHUP, cleanup_signal); signal(SIGPIPE, cleanup_signal); signal(SIGTERM, cleanup_signal); signal(SIGABRT, cleanup_signal); atexit(cleanup); if (send_intent(&su_from, &su_to, socket_path, -1, 0) < 0) { deny(); } if (socket_receive_result(socket_serv_fd, buf, sizeof(buf)) < 0) { deny(); } close(socket_serv_fd); socket_cleanup(); result = buf; if (!strcmp(result, "DENY")) { deny(); } else if (!strcmp(result, "ALLOW")) { allow(shell, orig_umask); } else { LOGE("unknown response from Superuser Requestor: %s", result); deny(); } deny(); return -1;
我们通过from_init判断一个调用su命令的APP的:PID、UID、调用程序的路径,调用程序的参数。不过“su_from.uid == AID_SHELL”看起来应该已经在后续的版本删除了,因为早自我们开始使用Z4ROOT时候,即使adb shell申请root也是需要用户允许的。函数allow()会放行,而deny()会拒绝,进入这两个流程后su就后续exit()了。如果调用程序不是UID=0(ROOT用户)时,需要后续判断完成操作,途中会访问数据库判断是否是记录在案的黑白名单,最后以SOCKET发送消息通知SuperUser.apk弹出窗口,由用户进行判断是否同意。
allow的函数实现,主要就是修改了申请SU的APK的UID和GID。需要说明一下的是,用户PID在进程结束时,可能下次启动会变化,但是UID是不会发生变化的,因此权限管理也是将UID作为唯一识别标识,用于维护黑白名单。allow函数主要实现代码如下所示:
struct su_request { unsigned uid; char *command; }; static struct su_request su_to = { .uid = AID_ROOT, .command = DEFAULT_COMMAND, }; static void allow(char *shell, mode_t mask) { //... setresgid(to->uid, to->uid, to->uid); setresuid(to->uid, to->uid, to->uid); LOGD("%u %s executing %u %s using shell %s : %s", from->uid, from->bin, to->uid, to->command, shell, exe); //... }
有关UID的设置函数原型:
int setresuid (uid_t ruid, uid_t euid, uid_t suid)
ruid=real uid; euid=effective uid; suid=saved set-user-id
setresuid()被执行的条件有:
①当前进程的euid是root
②三个参数,每一个等于原来某个id中的一个
这里是将其都设置为0(ROOT),注意我们这是跑于su-binary的代码,euid肯定是0(ROOT),因此这个设置是OK的,而申请ROOT的APP的在su申请部分获得了ROOT权限,比如我找了个网上的如下的代码:
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();
在我exit之前,这部分Shell命令都是跑在su的execl里面的,当我exit之后这个权限就会消失(退出)。例如说,我用RootExplorer做实验,当我启动RE浏览器的时候,RE浏览器申请到ROOT权限之后,我们在外面输入adb shell ps,可以看到如下数据:
6511是app_90进程(RootExplorer)的PID,其调用了一个Root Shell(/system/bin/sh/的PID是6518;su下运行的sh的PID是6520)
SuperUser.apk的代码主要就是接受su的消息,然后将其判断后仍然以SOCKET方式发送给su-binary,代码简介如下:
public void onReceive(Context context, Intent intent) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String automaticAction = prefs.getString(Preferences.AUTOMATIC_ACTION, "prompt"); if (automaticAction.equals("deny")) { sendResult(context, intent, false); return; } else if (automaticAction.equals("allow")) { sendResult(context, intent, true); return; } if (prefs.getBoolean("permissions_dirty", false)) { Log.d(TAG, "Database is dirty, check here"); String where = Apps.UID + "=? AND " + Apps.EXEC_UID + "=? AND " + Apps.EXEC_CMD + "=?"; Log.d(TAG, where); Cursor c = context.getContentResolver().query(Apps.CONTENT_URI, new String[] { Apps.ALLOW }, Apps.UID + "=? AND " + Apps.EXEC_UID + "=? AND " + Apps.EXEC_CMD + "=?", new String[] { String.valueOf(intent.getIntExtra(EXTRA_CALLERUID, -1)), String.valueOf(intent.getIntExtra(EXTRA_UID, -1)), String.valueOf(intent.getStringExtra(EXTRA_CMD)) }, null); if (c.moveToFirst()) { Log.d(TAG, "Found an entry"); switch (c.getInt(0)) { case Apps.AllowType.ALLOW: Log.d(TAG, "Allow"); sendResult(context, intent, true); break; case Apps.AllowType.DENY: Log.d(TAG, "Deny"); sendResult(context, intent, false); break; default: Log.d(TAG, "Prompt"); showPrompt(context, intent); } } else { Log.d(TAG, "No entry found, prompt"); showPrompt(context, intent); } c.close(); return; } int sysTimeout = prefs.getInt(Preferences.TIMEOUT, 0); if ( sysTimeout > 0) { String key = "active_" + intent.getIntExtra(EXTRA_CALLERUID, 0); long timeout = prefs.getLong(key, 0); if (System.currentTimeMillis() < timeout) { sendResult(context, intent, true); return; } else { showPrompt(context, intent); return; } } else { showPrompt(context, intent); return; } }
SendPrompt会弹框判断“是否”。
最后总结一下我们在输入adb shell ps时候经常看到的数据:
以root开头的其UID=0,以此类推常用的还有:
root uid = 0
system uid = 1000
app_X uid = 10000+X(比如app_90其UID=10090)
shell uid = 2000
这个UID可以在Android的源码中查询到:
#define AID_ROOT 0 /* traditional unix root user */ #define AID_SYSTEM 1000 /* system server */ #define AID_RADIO 1001 /* telephony subsystem, RIL */ #define AID_BLUETOOTH 1002 /* bluetooth subsystem */ #define AID_GRAPHICS 1003 /* graphics devices */ #define AID_INPUT 1004 /* input devices */ #define AID_AUDIO 1005 /* audio devices */ #define AID_CAMERA 1006 /* camera devices */ #define AID_LOG 1007 /* log devices */ #define AID_COMPASS 1008 /* compass device */ #define AID_MOUNT 1009 /* mountd socket */ #define AID_WIFI 1010 /* wifi subsystem */ #define AID_ADB 1011 /* android debug bridge (adbd) */ #define AID_INSTALL 1012 /* group for installing packages */ #define AID_MEDIA 1013 /* mediaserver process */ #define AID_DHCP 1014 /* dhcp client */ #define AID_SDCARD_RW 1015 /* external storage write access */ #define AID_VPN 1016 /* vpn system */ #define AID_KEYSTORE 1017 /* keystore subsystem */ #define AID_SHELL 2000 /* adb and debug shell user */ #define AID_CACHE 2001 /* cache access */ #define AID_DIAG 2002 /* access to diagnostic resources */ /* The 3000 series are intended for use as supplemental group id's only. * They indicate special Android capabilities that the kernel is aware of. */ #define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */ #define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */ #define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */ #define AID_NET_RAW 3004 /* can create raw INET sockets */ #define AID_NET_ADMIN 3005 /* can configure interfaces and routing tables. */ #define AID_MISC 9998 /* access to misc storage */ #define AID_NOBODY 9999 #define AID_APP 10000 /* first app user */
另外就是当我们知道PID时候,我们可以从/proc/<pid>/status里面直接查到UID,这是一种命令行查UID和GID的办法,比如我手机上的adb进程是5520时:
UID、GID包括一些进程参数可以通过这个方式用命令行直接查询。