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包括一些进程参数可以通过这个方式用命令行直接查询。

 

posted on 2013-10-08 19:56  堕落华为人  阅读(2590)  评论(0编辑  收藏  举报

导航