Android權(quán)限體系介紹

前言

本文主要針對(duì)一下幾個(gè)問(wèn)題伪货,通過(guò)查詢資料,和一些自己的思考著摔,梳理的一篇記錄

1爽待、什么是應(yīng)用沙盒? 為什么沙盒可以隔離保護(hù)app的數(shù)據(jù)

2腐魂、Android中 權(quán)限(網(wǎng)絡(luò)權(quán)限咧最、讀寫存儲(chǔ)權(quán)限) 控制的基礎(chǔ)是什么捂人?

3、看不見(jiàn)的權(quán)限管理(selinux)

一矢沿、Linux的用戶和用戶組

Linux有用戶和用戶組的概念先慷。在Linux系統(tǒng)中,每次登錄系統(tǒng)都必須以一個(gè)用戶的身份登錄咨察,并且登錄后的權(quán)限也會(huì)根據(jù)用戶身份來(lái)確定论熙。 每一個(gè)進(jìn)程在執(zhí)行時(shí),也會(huì)有其用戶摄狱,該用戶也和進(jìn)程所能控制的資源有關(guān)脓诡。Linux系統(tǒng)下的每一個(gè)目錄、文件媒役,都會(huì)有其屬于的用戶和用戶組祝谚,我們稱其為屬主和屬組。

在Linux系統(tǒng)中酣衷,每個(gè)用戶都有自己的用戶ID交惯,稱為UID,每個(gè)用戶組也有自己的用戶組ID穿仪,稱為GID席爽,UID和GID在Linux系統(tǒng)中是不可重復(fù)的。Linux系統(tǒng)就是通過(guò)UID和GID來(lái)對(duì)用戶和組進(jìn)行管理的啊片,而對(duì)于管理員來(lái)說(shuō)只锻,往往會(huì)設(shè)置用戶名和組名,這樣使得用戶和用戶組的使用管理更人性化紫谷。

在Linux系統(tǒng)中齐饮,一共有三種類型的用戶組捐寥。

  • root用戶
    root用戶時(shí)UID和GID都等于0的用戶,是Linux系統(tǒng)中的“上帝”祖驱,擁有最大的權(quán)限握恳。比如:無(wú)視Linux對(duì)權(quán)限的設(shè)置而強(qiáng)行讀、寫捺僻、執(zhí)行文件睡互,切換其他用戶登錄不需要密碼,可以強(qiáng)行切換到已經(jīng)所用的用戶陵像,只有root可以為普通用戶修改密碼等等。
  • 系統(tǒng)用戶
    系統(tǒng)用戶通常用于運(yùn)行服務(wù)寇壳,但是此用戶無(wú)home目錄醒颖,也不能用于登錄系統(tǒng)。例如壳炎,在yum安裝apache泞歉、nginx等服務(wù)后,就會(huì)自動(dòng)創(chuàng)建apache和nginx的用戶和同名用戶組匿辩。
  • 普通用戶
    普通用戶只能由root用戶創(chuàng)建腰耙,該用戶擁有home目錄,并且可以登錄铲球,該用戶的權(quán)限由root分配挺庞。普通用戶擁有指定的shell環(huán)境。

ls -ls 可以查看某個(gè)文件的讀寫執(zhí)行權(quán)限,共分為三組權(quán)限:owner的權(quán)限稼病、Group的權(quán)限选侨、和其他用戶的權(quán)限。如下圖所示:

權(quán)限_ls.png

二然走、Android 沙盒機(jī)制

android集成了Linux用戶和組的概念援制。Linux中通過(guò)用戶和用戶組將文件的權(quán)限進(jìn)行了隔離。Android中為每一個(gè)應(yīng)用賦予了一個(gè)用戶(owner)和用戶組(group),以實(shí)現(xiàn)應(yīng)用各自的文件彼此隔離的目錄芍瑞。這就是應(yīng)用的沙箱隔離機(jī)制晨仑。

android 應(yīng)用程序是沙箱隔離的,每個(gè)應(yīng)用都有一個(gè)只有自己具有讀寫權(quán)限的專用數(shù)據(jù)目錄拆檬,應(yīng)用只能訪問(wèn)自己的文件和一些設(shè)備上全局可訪問(wèn)的資源洪己。

沙盒_new_1.png

apk安裝之后,會(huì)被賦予一個(gè)uid和gid。uid和gid 通常相同竟贯。

通過(guò)/data/system/packages.list 查看對(duì)應(yīng)的包名,可以確定apk分配的uid

adb shell cat /data/system/packages.list | grep zappstore                                                                           
com.zuoyebang.iot.watch.zappstore 10045 0 /data/user/0/com.zuoyebang.iot.watch.zappstore platform:targetSdkVersion=27 3003,1007,3006

zappstore的uid為10045,10045-10000=45,uid的字符串形式為u0_a45

查看/data/data目錄的zappstore的沙盒文件的訪問(wèn)權(quán)限,可以發(fā)現(xiàn)

com.zuoyebang.iot.watch.zappstore 目錄所屬的用戶為u0_a45,用戶組為u0_a45, owner用戶u0_a45 對(duì)此目錄具有讀寫執(zhí)行權(quán)限,用戶組中其他用戶沒(méi)有任何權(quán)限,用戶組之外的其他用戶也沒(méi)有任何權(quán)限码泛。

sl8541e_1h10_go:/data/data # ls -ls | grep  zappstore                               
3 drwx------ 4 u0_a45    u0_a45    3488 2023-03-27 00:00 com.zuoyebang.iot.watch.zappstore

因此com.zuoyebang.iot.watch.zappstore沙盒目錄,只有zappstore的進(jìn)程才有訪問(wèn)權(quán)限,其他的app 無(wú)權(quán)此沙盒目錄的文件。

除了沙箱目錄,應(yīng)用進(jìn)程相關(guān)虛擬文件,也被限制為僅自己的uid可訪問(wèn)

user_id.png

共享UID

兩個(gè)應(yīng)用的具有相同的簽名文件澄耍,并且指令了shareUid為同一個(gè)uid,則這兩個(gè)應(yīng)用可以方便的訪問(wèn)彼此的私有目錄噪珊。

沙盒_new_2.png

比較常見(jiàn)的例子是:
Android中所有的系統(tǒng)應(yīng)用都指定了shareUid 為android.uid.system

android:sharedUserId="android.uid.system"

系統(tǒng)應(yīng)用apk 分配的uid都是1000,用戶名都是system

應(yīng)用沙盒的所屬的owner和group 都是system晌缘,因此系統(tǒng)應(yīng)用之間可以互相訪問(wèn)彼此的應(yīng)用沙盒。

這就是ShareUid機(jī)制

值得注意的是:Android 不支持將一個(gè)已安裝的應(yīng)用痢站,從非共享 UID 切換到共享狀態(tài)磷箕,因?yàn)楦淖兞艘寻惭b應(yīng)用的 UID,會(huì)導(dǎo)致應(yīng)用失去對(duì)自己文件的訪問(wèn)權(quán)限(在一些早期 Android 版本中)阵难,所以如果使用共享 UID 必須從一開(kāi)始就設(shè)計(jì)好岳枷。

三、權(quán)限的授予

幾個(gè)重要的配置文件:

  • /data/system/packages.xml
  • /data/system/users/0/runtime-permissions.xml呜叫。
  • /etc/permission/platform.xml
  • /system/core/include/private/android_filesystem_config.h

3.1空繁、權(quán)限管理

在每個(gè)應(yīng)用安裝時(shí),權(quán)限就已經(jīng)賦予了朱庆,系統(tǒng)使用包管理服務(wù)來(lái)管理權(quán)限盛泡。打開(kāi)我們系統(tǒng)目錄下的 /data/system/packages.xml,可以看到文件包含了所有已定義的權(quán)限列表和所有 apk 的包信息娱颊,這可以看做是包管理服務(wù)維護(hù)的一個(gè)已安裝程序的核心數(shù)據(jù)庫(kù)傲诵,這個(gè)數(shù)據(jù)庫(kù),隨著每次應(yīng)用安裝箱硕、升級(jí)或卸載而進(jìn)行更新拴竹。

package.xml
  • permissions標(biāo)簽內(nèi),定義了目前系統(tǒng)中的所有權(quán)限剧罩,分為系統(tǒng)內(nèi)置的(package 屬性為 android 的)和 apk 自定義的(package 屬性為 apk 的包名)
<permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        <item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
        <item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
        <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
        <item name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" package="android" protection="2" />
        <item name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" package="android" protection="18" />
        <item name="android.permission.BIND_INCALL_SERVICE" package="android" protection="18" />
        <item name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" package="android" protection="18" />
        ...
</permissions>
  • package標(biāo)簽栓拜, 包含了每個(gè)apk 的核心屬性。

Android 6.0 及以上 packages.xml

<package name="com.zuoyebang.iot.watch.zappstore" codePath="/system/app/zappstore" nativeLibraryPath="/system/app/zappstore/lib" primaryCpuAbi="armeabi-v7a" publicFlags="675855941" privateFlags="0" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="1" userId="10045" isOrphaned="true">
            <sigs count="1">
                <cert index="0" />
            </sigs>
            <perms>
                <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
                <item name="android.permission.MODIFY_AUDIO_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" />
                <item name="android.permission.INSTALL_PACKAGES" granted="true" flags="0" />
                <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
                <item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
                <item name="android.permission.INTERNET" granted="true" flags="0" />
                <item name="android.permission.STOP_APP_SWITCHES" granted="true" flags="0" />
                <item name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" granted="true" flags="0" />
                <item name="android.permission.WRITE_SECURE_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
                <item name="android.permission.DELETE_CACHE_FILES" granted="true" flags="0" />
                <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
                <item name="android.permission.READ_LOGS" granted="true" flags="0" />
                <item name="android.permission.READ_NETWORK_USAGE_HISTORY" granted="true" flags="0" />
                <item name="android.permission.VIBRATE" granted="true" flags="0" />
                <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
                <item name="android.permission.MODIFY_PHONE_STATE" granted="true" flags="0" />
                <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
                <item name="android.permission.DELETE_PACKAGES" granted="true" flags="0" />
            </perms>
            <proper-signing-keyset identifier="1" />
        </package>

Android 6.0以上normal權(quán)限 安裝之后自動(dòng)授予, 會(huì)枚舉在在perms列表中; 被標(biāo)記為dangrous的權(quán)限并不會(huì)列舉在 <perms>中,如:READ_SMS惠昔、READ_EXTERNAL_STORAGE菱属、CALL_PHONE 等。

Android 6.0 之前舰罚,權(quán)限都是在安裝時(shí)自動(dòng)賦予的纽门,不卸載應(yīng)用的情況下,不能更改或撤銷营罢。而 Android 6.0 版本對(duì) permission 的管理做了部分改動(dòng)赏陵,針對(duì) dangerous 級(jí)別,不再安裝的時(shí)候賦予權(quán)限饲漾,而是在運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)蝙搔。

/data/system/packages.xml 里保留的是安裝后不會(huì)再變更的權(quán)限(normal)

運(yùn)行時(shí)權(quán)限(dangrous)另外單獨(dú)地維護(hù)在
/data/system/users/0/runtime-permissions.xml中。

 <pkg name="com.example.testpermission">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
  </pkg>

3.2考传、權(quán)限授予

Android 應(yīng)用安裝時(shí)吃型,會(huì)被分配一個(gè)唯一的 UID,應(yīng)用啟動(dòng)時(shí)會(huì)設(shè)置新建進(jìn)程的 UID 和 GID 為應(yīng)用程序的 UID僚楞。如果應(yīng)用已經(jīng)被賦予了額外的權(quán)限勤晚,就把這些權(quán)限映射成一組 GID枉层,作為補(bǔ)充 GID 分配給進(jìn)程。底層就可以依賴于進(jìn)程的 UID赐写、GID 和補(bǔ)充 GID 來(lái)決定是否賦予權(quán)限了鸟蜡。

上面流程的重點(diǎn):

  • 權(quán)限是如何映射到 OS 層的 UID、GID 上的呢挺邀?
  • 映射完是怎么分配給進(jìn)程的揉忘?
  • 低層是怎么判斷是否賦予權(quán)限的?

/etc/permission/platform.xml

3.2.1端铛、權(quán)限和補(bǔ)充Gid的映射

內(nèi)置權(quán)限到 GID 的映射是定義在/etc/permission/platform.xml中

<permissions>
    ···
    <permission name="android.permission.READ_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
    </permission>

    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
        <group gid="sdcard_rw" />
    </permission>
    
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    ···
</permissions>

READ_EXTERNAL_STORAGE 這種運(yùn)行時(shí)權(quán)限泣矛,在 Android 6.0 之后已經(jīng)不會(huì)映射到 gid 了。動(dòng)態(tài)賦予禾蚕,動(dòng)態(tài)申請(qǐng)您朽,也就不需要映射了。

<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />

3.2.2夕膀、查看進(jìn)程授予的補(bǔ)充GID

adb shell ps 查看進(jìn)程的PID

adb shell ps | grep zappstore
u0_a45       10732   245 1230560 131600 SyS_epoll_wait b0cce590 S com.zuoyebang.iot.watch.zappstore

adb shell cat /proc/10732/status 查看進(jìn)程的狀態(tài)

sl8541e_1h10_go:/proc/10732 # cat status
Name:   watch.zappstore
State:  S (sleeping)
Tgid:   10732
Ngid:   0
Pid:    10732
PPid:   245
TracerPid:  0
Uid:    10045   10045   10045   10045
Gid:    10045   10045   10045   10045
FDSize: 256
Groups: 1007 3003 3006 9997 20045 50045 

可以看到Uid和Gid 為10045

Groups: 1007 3003 3006 9997 20045 50045

為此app所屬的輔助組,通過(guò)輔助組可以查看,該進(jìn)程被賦予了哪些權(quán)限

Android源碼android_filesystem_config.h 有輔助組的權(quán)限定義

\system\core\include\private\android_filesystem_config.h


#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 */
映射關(guān)系.png

3.2.3、進(jìn)程賦予Gid權(quán)限組

當(dāng)我們安裝應(yīng)用完成美侦,啟動(dòng)應(yīng)用产舞,應(yīng)用的進(jìn)程是如何啟動(dòng)并被賦予進(jìn)程屬性

每個(gè)應(yīng)用都會(huì)運(yùn)行在自己的 Dalvik 虛擬機(jī)進(jìn)程中,但是為了提高啟動(dòng)效率菠剩,Android 不會(huì)為每個(gè)應(yīng)用都新建一個(gè) Dalvik 進(jìn)程易猫,而是采用 fork 的形式。每個(gè)進(jìn)程都 fork form zygote 進(jìn)程具壮。

當(dāng) zygote 收到啟動(dòng)新進(jìn)程的請(qǐng)求時(shí)准颓,它會(huì) fork 自身出一個(gè)子進(jìn)程,并對(duì)該子進(jìn)程做特殊化處理棺妓。其源代碼位于 dalvik/vm/native/dalvik_system_Zygote.c 中攘已。forkAndSpecializeCommon() 的

static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer)  
{  
 ...  
 pid = fork();  //創(chuàng)建新進(jìn)程  
 if (pid == 0)  //判斷是否是root,有沒(méi)有權(quán)限修改自己的進(jìn)程屬性
 {  
  setgroupsIntarray(gids);  //設(shè)置進(jìn)程的所有組  
  setrlimitsFromArray(rlimits);  
  setgid(gid);    //設(shè)置進(jìn)程的組ID  
  setuid(uid);    //設(shè)置進(jìn)程的用戶ID  
     }  
  ...  
} 

這里設(shè)置進(jìn)程的組 ID 和用戶 ID怜跑,通過(guò) fork 創(chuàng)建的子進(jìn)程調(diào)用 setgroups Intarray 設(shè)置該進(jìn)程所屬的組样勃,這樣應(yīng)用程序就擁有了該組的權(quán)限,并且可以通過(guò) setgid() 及 setuid() 確定應(yīng)用程序的 GID 及 UID 值性芬。

3.2.4峡眶、權(quán)限檢查(權(quán)限執(zhí)行)

1). 系統(tǒng)內(nèi)核層權(quán)限檢查

以 android.permission.INTERNET 網(wǎng)絡(luò)權(quán)限為例,Android系統(tǒng)是如何檢查一個(gè)應(yīng)用是否有網(wǎng)絡(luò)訪問(wèn)權(quán)限的

Android 的訪問(wèn)控制植锉,和 Linux 是一樣的辫樱,但 Android 增加了個(gè)特有的網(wǎng)絡(luò)訪問(wèn)安全控制機(jī)制,也就是說(shuō)俊庇,創(chuàng)建網(wǎng)絡(luò)套接字的進(jìn)程狮暑,必須屬于 inet 組鸡挠。

我們看下kernel中的相關(guān)代碼,kernel/apv4/af_inet.c


#ifdef CONFIG_ANDROID_PARANOID_NETWORK
#include <linux/android_aid.h>

static inline int current_has_network(void)
{
    return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
#else
static inline int current_has_network(void)
{
    return 1;
}
#endif



/*
 *  Create an inet socket.
 */

static int inet_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{

    ...


    //判斷當(dāng)前進(jìn)程是否具有網(wǎng)絡(luò)權(quán)限;沒(méi)有權(quán)限則直接返回
    if (!current_has_network())
        return -EACCES;

   
    ...

}

如上內(nèi)核代碼心例,current_has_network(void) 方法檢查了進(jìn)程的所在組宵凌。如果不在 inet 組,則直接返回錯(cuò)誤止后。所以為了使我們的應(yīng)用具有訪問(wèn)網(wǎng)絡(luò)的能力瞎惫,我們需要在 AndroidManifest.xml 中申請(qǐng) INTERNET 權(quán)限,經(jīng)過(guò)解析译株,逐步映射到內(nèi)核層的組 ID 和用戶 ID瓜喇,最終才能通過(guò)內(nèi)核層的檢查。

2)非內(nèi)核層的其他 C/C++ 層歉糜,如何拿到進(jìn)程的所在組信息

在 PMS 初始化所有包信息之后,就會(huì)調(diào)用 mSettings.writePackageListLPr()將mPackages 中保存的所有包的信息保存到 /data/system/packages.list乘寒,

frameworks/base/services/core/java/com/android/server/pm/Settings

 void writePackageListLPr(int creatingUserId) {
        // Only derive GIDs for active users (not dying)
        final List<UserInfo> users = UserManagerService.getInstance().getUsers(true);
        int[] userIds = new int[users.size()];
        for (int i = 0; i < userIds.length; i++) {
            userIds[i] = users.get(i).id;
        }
        if (creatingUserId != -1) {
            userIds = ArrayUtils.appendInt(userIds, creatingUserId);
        }

        // Write package list file now, use a JournaledFile.
        File tempFile = new File(mPackageListFilename.getAbsolutePath() + ".tmp");
        JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile);

        final File writeTarget = journal.chooseForWrite();
        FileOutputStream fstr;
        BufferedWriter writer = null;
        try {
            fstr = new FileOutputStream(writeTarget);
            writer = new BufferedWriter(new OutputStreamWriter(fstr, Charset.defaultCharset()));
            FileUtils.setPermissions(fstr.getFD(), 0640, SYSTEM_UID, PACKAGE_INFO_GID);

            StringBuilder sb = new StringBuilder();
            for (final PackageSetting pkg : mPackages.values()) {
                if (pkg.pkg == null || pkg.pkg.applicationInfo == null
                        || pkg.pkg.applicationInfo.dataDir == null) {
                    if (!"android".equals(pkg.name)) {
                        Slog.w(TAG, "Skipping " + pkg + " due to missing metadata");
                    }
                    continue;
                }

                final ApplicationInfo ai = pkg.pkg.applicationInfo;
                final String dataPath = ai.dataDir;
                final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                final int[] gids = pkg.getPermissionsState().computeGids(userIds);

                // Avoid any application that has a space in its path.
                if (dataPath.indexOf(' ') >= 0)
                    continue;

                // we store on each line the following information for now:
                //
                // pkgName    - package name
                // userId     - application-specific user id
                // debugFlag  - 0 or 1 if the package is debuggable.
                // dataPath   - path to package's data path
                // seinfo     - seinfo label for the app (assigned at install time)
                // gids       - supplementary gids this app launches with
                //
                // NOTE: We prefer not to expose all ApplicationInfo flags for now.
                //
                // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
                // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
                //   frameworks/base/libs/packagelistparser
                //   system/core/run-as/run-as.c
                //
                sb.setLength(0);
                sb.append(ai.packageName);
                sb.append(" ");
                sb.append(ai.uid);
                sb.append(isDebug ? " 1 " : " 0 ");
                sb.append(dataPath);
                sb.append(" ");
                sb.append(ai.seInfo);
                sb.append(" ");
                if (gids != null && gids.length > 0) {
                    sb.append(gids[0]);
                    for (int i = 1; i < gids.length; i++) {
                        sb.append(",");
                        sb.append(gids[i]);
                    }
                } else {
                    sb.append("none");
                }
                sb.append("\n");
                writer.append(sb);
            }
            writer.flush();
            FileUtils.sync(fstr);
            writer.close();
            journal.commit();
        } catch (Exception e) {
            Slog.wtf(TAG, "Failed to write packages.list", e);
            IoUtils.closeQuietly(writer);
            journal.rollback();
        }
    }

packages.list中保存了所有應(yīng)用申請(qǐng)的權(quán)限,C代碼只要讀這個(gè)文件就能判斷某個(gè)應(yīng)用是否申請(qǐng)了我們要求的權(quán)限匪补。

/data/system/packages.list

com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003
com.android.bluetoothmidiservice 10022 0 /data/user/0/com.android.bluetoothmidiservice platform:targetSdkVersion=27 3002
plugin.sprd.vodafonefeatures 10024 0 /data/user/0/plugin.sprd.vodafonefeatures platform:targetSdkVersion=27 none

3) 框架層 PackageManagerService 系統(tǒng)包管理器會(huì)負(fù)責(zé)記錄組件的權(quán)限.

使用 Binder.getCallingUid()Binder.getCallingPid() 獲取調(diào)用者的 UID 和 PID伞辛,通過(guò) UID 在包管理器中查詢到對(duì)應(yīng)應(yīng)用的權(quán)限。


android.content.Context 類中有 checkPermission(String permission, int pid, int uid) 方法 實(shí)質(zhì)上會(huì)調(diào)用到 PMS 中的 checkUidPermission(String perName, int uid)

  • Android 6.0 以下 PMS 中的 checkUidPermission(String perName, int uid)

    public int checkUidPermission(String permName, int uid) {
        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                GrantedPermissions gp = (GrantedPermissions)obj;
                if (gp.grantedPermissions.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                HashSet<String> perms = mSystemPermissions.get(uid);
                if (perms != null && perms.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }
    

Android 6.0 以下的 checkUidPermission() 方法比較簡(jiǎn)單夯缺,首先蚤氏,基于入?yún)?uid 獲取應(yīng)用的 appId,拿到權(quán)限列表對(duì)象(也就是 packages.xml 里的 <package> 映射)踊兜,如果 GrantedPermissions 類中的 grantedPermissions 集合包含目標(biāo)權(quán)限竿滨,則檢查通過(guò)。

  • Android 6.0 及以上 PMS 中的 checkUidPermission(String perName, int uid)
 @Override
    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                ...
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
                ...
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    ...
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

6.0 之后 checkPermission() 方法有所改變捏境。多了從 mSettings.mPermissions 去查詢權(quán)限列表于游。

關(guān)鍵就在于這個(gè) mSettings 里面保存的這個(gè) SettingBase 對(duì)象,它記錄了 PermissionsState 也就是權(quán)限的授予情況垫言。

// PermissionsState.java
public boolean hasPermission(String name, int userId) {
    enforceValidUserId(userId);

    if (mPermissions == null) {
        return false;
    }

    PermissionData permissionData = mPermissions.get(name);
    return permissionData != null && permissionData.isGranted(userId);
}

3.3贰剥、小結(jié)

  • /data/system/packages.xml

    安裝即授予的權(quán)限(normal權(quán)限) 會(huì)記錄在packages.xml 對(duì)應(yīng)package包名下的perms節(jié)點(diǎn) 。管理普通權(quán)限筷频,且默認(rèn)granted.

    <package name="com.example.testpermission" codePath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==" nativeLibraryPath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==/lib" publicFlags="944291654" privateFlags="0" ft="1879dad5090" it="1879d8dde0b" ut="1879dad52af" version="1" userId="10056">
        <sigs count="1">
            <cert index="8" />
        </sigs>
        <perms>
            <item name="android.permission.INTERNET" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="12" />
    </package>
  • /data/system/users/0/runtime-permissions.xml鸠澈。

管理運(yùn)行時(shí)權(quán)限,未requestPermissions()不會(huì)有對(duì)應(yīng)app的pkg標(biāo)簽截驮。

<pkg name="com.example.testpermission">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
  </pkg>
  • /etc/permission/platform.xml

記錄了權(quán)限和Gid的映射關(guān)系(normal 權(quán)限有效)笑陈,僅普通權(quán)限會(huì)記錄在此處

<permissions>
    ···
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    ···
</permissions>
  • /system/core/include/private/android_filesystem_config.h

    android_filesystem_config.h 定義了AID的具體內(nèi)容 和權(quán)限組gid相對(duì)應(yīng)。

    
    #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 */
    
  • /data/system/packages.list packages.list中保存了所有應(yīng)用申請(qǐng)的權(quán)限

packages.list保存了應(yīng)用安裝后,分配的uid葵袭、gid 和補(bǔ)充組gid

com.android.emergency 10010 0 /data/user_de/0/com.android.emergency platform:privapp:targetSdkVersion=27 none
com.android.location.fused 1000 0 /data/user_de/0/com.android.location.fused platform:privapp:targetSdkVersion=27 2001,1013,3002,1023,1015,3003,3001,1007,3006
com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003

四涵妥、SELinux

4.1、基本概念

SELinux 全稱 Security Enhanced Linux (安全強(qiáng)化 Linux)坡锡,是MAC (Mandatory Access Control蓬网,強(qiáng)制訪問(wèn)控制系統(tǒng))的一個(gè)實(shí)現(xiàn)窒所。其目的在于明確的指明某個(gè)進(jìn)程可以訪問(wèn)哪些資源(文件、網(wǎng)絡(luò)端口等)帆锋。Android系統(tǒng)基于Linux實(shí)現(xiàn)吵取。針對(duì)傳統(tǒng)Linux系統(tǒng),NSA開(kāi)發(fā)了一套安全機(jī)制SELinux锯厢,用來(lái)加強(qiáng)安全性皮官。然而,由于Android系 統(tǒng)有著獨(dú)特的用戶空間運(yùn)行時(shí)实辑,因此SELinux不能完全適用于Android系統(tǒng)捺氢。為此,NSA同Google一起針對(duì)Android系統(tǒng)剪撬,在SELinux基礎(chǔ)上開(kāi)發(fā)了 SEAndroid摄乒。

1) DAC和MAC的區(qū)別:

DAC核心思想:進(jìn)程理論上所擁有的權(quán)限與執(zhí)行它的用戶的權(quán)限相同。比如残黑,以root用戶啟動(dòng)Browser馍佑,那么Browser就有root用戶的權(quán)限,在Linux系統(tǒng)上能干任何事情梨水。

MAC核心思想:即任何進(jìn)程想在SELinux系統(tǒng)中干任何事情拭荤,都必須先在安全策略配置文件中賦予權(quán)限。凡是沒(méi)有出現(xiàn)在安全策略配置文件中的權(quán)限冰木,進(jìn)程就沒(méi)有該權(quán)限穷劈。

2) DAC和MAC的聯(lián)系:

SELinux 通過(guò)MAC的方式來(lái)控管程序,控制的主題是進(jìn)程笼恰,客體是該程序能否讀寫的資源(文件)

  • 主體(subject):進(jìn)程

  • 目標(biāo)(Object):被訪問(wèn)的資源(可以是文件踊沸、目錄、端口社证、設(shè)備等)

  • 政策(policy):訪問(wèn)限制條件

  • 安全上下文(security context)
    主體能不能存取目標(biāo)除了政策之外逼龟,主體與目標(biāo)的安全性文本必須一致才能夠順利獲取

3)DAC、MAC 驗(yàn)證順序:

Android系統(tǒng) 會(huì)首先驗(yàn)證DAC權(quán)限 驗(yàn)證通過(guò)后,會(huì)驗(yàn)證MAC是否授予權(quán)限

4.2追葡、Security Context 安全上下文

SEAndroid是一種基于安全策略的MAC 安全機(jī)制腺律。這種安全策略又是建立在對(duì)象的安全上下文的基礎(chǔ)上的。這里所說(shuō)的對(duì)象分為兩種類型宜肉,一種稱 主體(Subject)匀钧,一種稱為客體(Object)。主體通常就是指進(jìn)程谬返,而客體就是指進(jìn)程所要訪問(wèn)的資源之斯,例如文件、系統(tǒng)屬性等遣铝。

4.2.1佑刷、什么安全上下文

安全上下文實(shí)際上就是一個(gè)附加在對(duì)象上的標(biāo)簽(label)莉擒。

進(jìn)程A 對(duì)文件B進(jìn)行讀操作,系統(tǒng)會(huì)檢查A的安全上下文標(biāo)簽,對(duì)B的安全上下文標(biāo)簽 是否具有讀權(quán)限瘫絮。

這個(gè)標(biāo)簽實(shí)際上就是一個(gè)字符串涨冀,它由四部分內(nèi)容組成,分別是SELinux用戶麦萤、SELinux 角色鹿鳖、類型、安全級(jí)別频鉴,每一個(gè)部分都通過(guò)一個(gè)冒號(hào)來(lái)分隔栓辜,格式為“user:role:type:rank”。

SELinux中 每種東西都被賦予了一個(gè)安全屬性,稱為Security Context (安全上下文),它是一個(gè)字符串垛孔。 Scontext分為:進(jìn)程(主體)的scontext 和 文件(客體)的tcontext藕甩。兩者匹配時(shí),才會(huì)允許進(jìn)程訪問(wèn)文件,若不匹配時(shí)則不允許訪問(wèn)周荐。

安全上下文和標(biāo)簽.png

4.2.2狭莱、查看進(jìn)程的安全上下文:

進(jìn)程的 Security Context 可通過(guò) ps -(A)Z 命令查看
adb shell ps -Z

u:r:hal_wifi_supplicant_default:s0 wifi      1033     1   11488   7592 0                   0 S wpa_supplicant
u:r:radio:s0                   radio         1044   245 1174504 114696 0                   0 S com.android.phone
u:r:system_app:s0              system        1189   245 1179352 103264 0                   0 S

4.2.3、查看文件的安全上下文:

文件的 Secure Context 可以通過(guò) ls (file) -Z 來(lái)查看:

# adb shell ls /data/data/ -Z 
u:object_r:system_app_data_file:s0    com.android.inputdevices             u:object_r:system_app_data_file:s0    com.zuoyebang.iot.watch.zalarm         
u:object_r:system_app_data_file:s0 


u :user概作,SEAndroid 中定義了一個(gè)用戶
r : role腋妙,一個(gè) u 可以屬于多個(gè) role,不同的 role 具有不同的權(quán)限讯榕。在SEAndroid中的role有兩個(gè)骤素,分別為 r 和 object_r
object_r:role,文件是死的東西愚屁,在 SELinux 中济竹,死的東西都用 object_r 來(lái)表示它的 role
platform_app :type,表示該進(jìn)程所屬的 type 為 platform_app
adb_data_file :type霎槐,表示 adb 文件夾所屬的 type 為 adb_data_file
s0:c512 : sensitivity送浊,category ,是 SELinux 為了滿足軍用和教育行業(yè)而設(shè)計(jì)的 Multi-Level Security(MLS)機(jī)制丘跌。MLS 將系統(tǒng)的進(jìn)程和文件進(jìn)行了分級(jí)袭景,不同級(jí)別的資源需要對(duì)應(yīng)級(jí)別的進(jìn)程才能訪問(wèn)

在安全上下文中,只有類型(Type)才是最重要的闭树,SELinux用戶耸棒、SELinux角色和安全級(jí)別都幾乎可以忽略不計(jì)的。正因?yàn)槿绱吮ㄈ瑁琒EAndroid安全機(jī)制又稱為是基于TE(Type Enforcement)策略的安全機(jī)制与殃。

4.3、安全策略 TE

安全策略.png

安全策略是在安全上下文的基礎(chǔ)上進(jìn)行描述的,它通過(guò)主體和客體的安全上下文,定義主體是否有權(quán)限訪問(wèn)客體。SEAndroid安全機(jī)制主要是使用安全上下文中的類型來(lái)定義安全策略奈籽,這種安全策略稱為Type EnforceMent,簡(jiǎn)稱TE饥侵。所有以.te為后綴的文件經(jīng)過(guò)編譯后都會(huì)生成一個(gè)sepolicy文件。這個(gè)policy文件會(huì)打包在ROM中,并且保存在設(shè)備上衣屏。

根據(jù)Selinux規(guī)范躏升,完整的allow相關(guān)語(yǔ)句格式為:
rule_name:source_type:target_type:class perm_set

source_type和target_type 都是查看進(jìn)程和文件的安全上下文得來(lái)的。

安全策略規(guī)則_2.png

備注:
SEAndroid 將app分了幾類:

  • platform_app.te (具有android platfrom簽名,但是沒(méi)有system權(quán)限)
  • system_app.te(具有android platfrom簽名 與system權(quán)限)
  • untrusted_app.te (第三方app,不具有android platform簽名和系統(tǒng)權(quán)限)

allow 語(yǔ)句的例子:

- 允許zygote domain 中的進(jìn)程向init type的進(jìn)程 (Object class 為process) 發(fā)送sigchld信號(hào)
    allow zygote init\:procss sigchld;

- 允許zygote域中的進(jìn)程search 或getattr 類型為appdomain的目錄狼忱。注意 多個(gè)perm\_set用{}括起來(lái)
    allow zygote appdomain\:dir{getattr search};

4.4膨疏、SELinux 相關(guān)設(shè)置

強(qiáng)制執(zhí)行等級(jí)
熟悉以下術(shù)語(yǔ),了解如何按不同的強(qiáng)制執(zhí)行級(jí)別實(shí)現(xiàn) SELinux

  • 寬容模式(permissive) - 僅記錄但不強(qiáng)制執(zhí)行 SELinux 安全政策钻弄。
  • 強(qiáng)制模式(enforcing) - 強(qiáng)制執(zhí)行并記錄安全政策佃却。如果失敗,則顯示為 EPERM 錯(cuò)誤窘俺。

4.4.1饲帅、 關(guān)閉 SELinux

臨時(shí)關(guān)閉
(1) setenforce
setenforce [ Enforcing | Permissive | 1 | 0 ]
setenforce 命令修改的是 /sys/fs/selinux/enforce 節(jié)點(diǎn)的值,是 kernel 意義上的修改 selinux 的策略瘤泪。斷電之后灶泵,節(jié)點(diǎn)值會(huì)復(fù)位

(2) getenforce

返回結(jié)果有兩種:Enforcing和Permissive. Permissive 代表SELinux關(guān)閉,不會(huì)阻止進(jìn)程違反SELinux策略訪問(wèn)資源的行為对途。Enforcing 代表SELinux處于開(kāi)啟狀態(tài)赦邻,會(huì)阻止進(jìn)程違反SELinux策略訪問(wèn)資源的行為。

永久關(guān)閉
(1) kernel 關(guān)閉 selinux

SECURITY_SELINUX 設(shè)置為 false实檀,重新編譯 kernel1

4.5惶洲、自定義te文件

場(chǎng)景: 為 app_permission.sh 腳本賦予 相應(yīng)的權(quán)限,使其可以開(kāi)機(jī)自動(dòng)運(yùn)行

定義apppermission.te 定義apppermission進(jìn)程type和app_

4.5.1、定義app_permission和app_permission_exec

  • 定義app_permission 和app_permission_exec 的Type類型

android8go_watch_os/device/sprd/sharkle/common/platsepolicy/public/app_permission.te


##定義app_perssion 進(jìn)程域
type app_permission, domain, mlstrustedsubject;
##定義app_permission_exec 可執(zhí)行文件類型
type app_permission_exec, exec_type,file_type;

  • 定義app_permission 屬性膳犹、init_daemon_domain切換進(jìn)程域

android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/app_permission.te


typeattribute app_permission coredomain;
##切換app_permission 進(jìn)程域
init_daemon_domain(app_permission)

  • 賦予app_permission進(jìn)程域類型 相應(yīng)的selinux權(quán)限

    android8go_watch_os/device/sprd/sharkle/common/sepolicy/app_permission.te

    #fanlongjun add for aplog!
    #binder_use(app_permission)
    allow app_permission shell_exec:file {read open execute getattr};
    allow app_permission toolbox_exec:file {read open execute getattr execute_no_trans};
    allow app_permission logcat_exec:file {  getattr execute read open execute_no_trans };   #call "logcat"
    #allow app_permission vendor_toolbox_exec:file {read getattr execute};
    allow app_permission storage_file:dir {search getattr};
    allow app_permission app_permission:capability {chown sys_admin dac_override net_raw sys_nice setuid setgid sys_nice fsetid};
    allow app_permission sdcardfs:dir {create write search add_name read remove_name open rmdir rename reparent setattr};
    allow app_permission sdcardfs:file {read append create open getattr write unlink rename setattr};
    allow app_permission media_rw_data_file:dir {create write search add_name read remove_name open rmdir rename reparent setattr getattr};
    allow app_permission media_rw_data_file:file {read append create open getattr write unlink rename setattr};
    allow app_permission logdr_socket:sock_file {write};
    allow app_permission logd:unix_stream_socket {connectto};
    allow app_permission system_file:file {read open execute getattr execute_no_trans};
    allow app_permission storage_file:lnk_file {read getattr};
    allow app_permission mnt_user_file:dir  {  search read open write add_name remove_name };
    allow app_permission mnt_user_file:lnk_file {  read open write };
    
    allow app_permission system_app_data_file:dir {getattr read};
    allow app_permission system_data_file:file {getattr read};
    allow app_permission app_data_file:dir { read setattr getattr};
    allow app_permission self:capability fowner;
    
    

4.5.2恬吕、為app_permission.sh 文件實(shí)體 綁定app_permission_exec 安全上下文標(biāo)簽

android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/file_contexts


#為app_permission.sh 綁定安全上下文
/system/bin/app_permission.sh      u:object_r:app_permission_exec:s0

五、參考文章

Android 安全架構(gòu)

http://www.reibang.com/p/5284fd388394

Android UID/GID

https://blog.csdn.net/weixin_40366279/article/details/121281882

SEAndroid安全機(jī)制框架分析

SELinux用audio2allow生成添加權(quán)限的格式及neverallow解決方法

Linux學(xué)習(xí) - SELinux/SEAndroid 不錯(cuò)的文章

Android 權(quán)限配置文件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末镣奋,一起剝皮案震驚了整個(gè)濱河市币呵,隨后出現(xiàn)的幾起案子怀愧,更是在濱河造成了極大的恐慌侨颈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芯义,死亡現(xiàn)場(chǎng)離奇詭異哈垢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)扛拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耘分,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事求泰⊙朐” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵渴频,是天一觀的道長(zhǎng)芽丹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)卜朗,這世上最難降的妖魔是什么拔第? 我笑而不...
    開(kāi)封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮场钉,結(jié)果婚禮上蚊俺,老公的妹妹穿的比我還像新娘。我一直安慰自己逛万,他們只是感情好泳猬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著宇植,像睡著了一般暂殖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上当纱,一...
    開(kāi)封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天呛每,我揣著相機(jī)與錄音,去河邊找鬼坡氯。 笑死晨横,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的箫柳。 我是一名探鬼主播手形,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼悯恍!你這毒婦竟也來(lái)了库糠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涮毫,失蹤者是張志新(化名)和其女友劉穎瞬欧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體罢防,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艘虎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咒吐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片野建。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡属划,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出候生,到底是詐尸還是另有隱情同眯,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布唯鸭,位于F島的核電站嗽测,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肿孵。R本人自食惡果不足惜唠粥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望停做。 院中可真熱鬧晤愧,春花似錦、人聲如沸蛉腌。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烙丛。三九已至舅巷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間河咽,已是汗流浹背钠右。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忘蟹,地道東北人飒房。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像媚值,于是被迫代替她去往敵國(guó)和親狠毯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容